('a[href="/issues/PAP-120"]');
- expect(parent?.textContent).toContain("Prepare the engineering team");
- expect(parent?.getAttribute("aria-label")).toBe("Prepare the engineering team PAP-120");
+ expect(parent?.textContent).toBe("Prepare the engineering team");
+ expect(parent?.querySelector('[data-slot="task-title-identifier"]')).toBeNull();
+ expect(parent?.getAttribute("aria-label")).toBe("Prepare the engineering team");
expect(container.querySelector('[aria-current="page"]')?.textContent).toContain("PAP-16679");
if (mobile) {
expect(container.querySelector('a[href="/issues"]')).toBeNull();
diff --git a/ui/src/pages/IssueDetail.test.tsx b/ui/src/pages/IssueDetail.test.tsx
index d9bf712625b..1bc7cab7e9f 100644
--- a/ui/src/pages/IssueDetail.test.tsx
+++ b/ui/src/pages/IssueDetail.test.tsx
@@ -2923,7 +2923,7 @@ describe("IssueDetail", () => {
expect(container.textContent).toContain("Issue detail smoke");
expect(mockSetBreadcrumbs).toHaveBeenLastCalledWith([
{ label: "Tasks", href: "/issues" },
- expect.objectContaining({ label: "Parent task visible in Properties", identifier: "PAP-0", href: "/issues/PAP-0" }),
+ expect.objectContaining({ label: "Parent task visible in Properties", href: "/issues/PAP-0" }),
expect.objectContaining({ label: "Issue detail smoke" }),
]);
});
@@ -2939,6 +2939,8 @@ describe("IssueDetail", () => {
await act(async () => { renderIssueView(); });
await flushReact();
const parentCrumb = mockSetBreadcrumbs.mock.calls.at(-1)![0][1];
+ expect(parentCrumb.identifier).toBeUndefined();
+ expect(parentCrumb.label).toBe("Parent task");
expect(parentCrumb.onClick).toBeTypeOf("function");
parentCrumb.onClick();
const expected = { label: source === "query" ? "Inbox" : "Mine", href: state.issueDetailBreadcrumb!.href };
@@ -2951,7 +2953,9 @@ describe("IssueDetail", () => {
mockIssuesApi.get.mockResolvedValue(createIssue({ id: "parent-1", identifier: "PAP-0", title: "Parent task" }));
await act(async () => { renderIssueView(); });
await waitForAssertion(() => {
- expect(mockSetBreadcrumbs.mock.calls.at(-1)![0][0]).toEqual(expected);
+ const breadcrumbs = mockSetBreadcrumbs.mock.calls.at(-1)![0];
+ expect(breadcrumbs[0]).toEqual(expected);
+ expect(breadcrumbs.at(-1)?.label).toBe(parentCrumb.label);
});
});
@@ -2964,19 +2968,20 @@ describe("IssueDetail", () => {
await flushReact();
expect(mockSetBreadcrumbs).toHaveBeenLastCalledWith([
{ label: "Tasks", href: "/issues" },
- expect.objectContaining({ label: "Private task", identifier: "PAP-0", href: undefined }),
+ expect.objectContaining({ label: "Private task", href: undefined }),
expect.objectContaining({ label: "Issue detail smoke" }),
]);
const parentCrumb = mockSetBreadcrumbs.mock.calls.at(-1)![0][1];
+ expect(parentCrumb.identifier).toBeUndefined();
expect(parentCrumb.onClick).toBeUndefined();
await act(async () => { renderIssueView(parentCrumb.control); });
const chip = container.querySelector('[data-testid="locked-issue-chip"]');
expect(chip).not.toBeNull();
- expect(chip?.textContent).toBe(isMobile ? "Private" : "PAP-0");
+ expect(chip?.textContent).toBe("Private");
expect(container.querySelector('a, [role="link"]')).toBeNull();
});
- it("updates the private parent label when an open task changes viewport", async () => {
+ it("keeps the private parent title free of IDs when an open task changes viewport", async () => {
mockIssuesApi.get.mockResolvedValue(createIssue({
ancestors: [{ id: "private-parent", identifier: "PAP-0", locked: true }] as unknown as Issue["ancestors"],
}));
@@ -3000,7 +3005,7 @@ describe("IssueDetail", () => {
await act(async () => {
renderIssueView();
});
- for (const [index, expected] of ["PAP-0", "Private", "PAP-0"].entries()) {
+ for (const [index, expected] of ["Private", "Private", "Private"].entries()) {
await act(async () => { updateBreadcrumbs(captured[index]); });
expect(container.querySelector('[data-testid="locked-issue-chip"]')?.textContent).toBe(expected);
expect(container.querySelector('a, [role="link"]')).toBeNull();
diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx
index 9846dc0591d..ac17f30eb87 100644
--- a/ui/src/pages/IssueDetail.tsx
+++ b/ui/src/pages/IssueDetail.tsx
@@ -5994,15 +5994,14 @@ export function TaskDetailSurface({ conversation, tasksTab }: { tasksTab?: TaskS
sourceBreadcrumb,
...(breadcrumbParent ? [{
label: isLockedIssueStub(breadcrumbParent) ? "Private task" : breadcrumbParent.title,
- identifier: breadcrumbParent.identifier ?? undefined,
href: isLockedIssueStub(breadcrumbParent)
? undefined
: createIssueDetailPath(breadcrumbParent.identifier ?? breadcrumbParent.id),
onClick: isLockedIssueStub(breadcrumbParent) ? undefined : rememberBreadcrumbParent,
control: isLockedIssueStub(breadcrumbParent)
- ?
+ ?
: undefined,
- controlKey: isLockedIssueStub(breadcrumbParent) ? `private-parent:${isMobile}` : undefined,
+ controlKey: isLockedIssueStub(breadcrumbParent) ? "private-parent" : undefined,
}] : []),
{
// The status glyph (leading) already conveys in-progress/live state;
diff --git a/ui/storybook/stories/task-coordination/task-breadcrumbs.stories.tsx b/ui/storybook/stories/task-coordination/task-breadcrumbs.stories.tsx
index 384793e537f..42c09648e10 100644
--- a/ui/storybook/stories/task-coordination/task-breadcrumbs.stories.tsx
+++ b/ui/storybook/stories/task-coordination/task-breadcrumbs.stories.tsx
@@ -5,7 +5,6 @@ import { BreadcrumbBar } from "@/components/BreadcrumbBar";
import { LockedIssueChip } from "@/components/LockedIssueChip";
import { StatusIcon } from "@/components/StatusIcon";
import { useBreadcrumbs } from "@/context/BreadcrumbContext";
-import { useSidebar } from "@/context/SidebarContext";
import { useLocation } from "@/lib/router";
import { PluginLauncherProvider } from "@/plugins/launchers";
@@ -23,21 +22,19 @@ function TaskBreadcrumbPreview({
source?: "Tasks" | "Mine";
}) {
const { setBreadcrumbs } = useBreadcrumbs();
- const { isMobile } = useSidebar();
const location = useLocation();
useEffect(() => {
setBreadcrumbs([
{ label: source, href: source === "Mine" ? "/inbox" : "/issues" },
...(showParent ? [{
label: privateParent ? "Private task" : parentTitle,
- identifier: "PAP-120",
href: privateParent ? undefined : "/issues/PAP-120",
- control: privateParent ? : undefined,
- controlKey: privateParent ? `private-parent:${isMobile}` : undefined,
+ control: privateParent ? : undefined,
+ controlKey: privateParent ? "private-parent" : undefined,
}] : []),
{ label: title, identifier: "PAP-121", leading: , leadingKey: "in_progress" },
]);
- }, [parentTitle, title, showParent, privateParent, source, isMobile, setBreadcrumbs]);
+ }, [parentTitle, title, showParent, privateParent, source, setBreadcrumbs]);
return (
@@ -67,7 +64,7 @@ const meta = {
status: "current",
implementation: "production",
boundary: "fixtures",
- notes: "Mounts the shipped BreadcrumbBar with fixture breadcrumb context. Covers linked immediate parent, long titles, private parent, standalone task, Mine source, light/dark themes and responsive widths. Page data loading and parent authorization are outside this isolated header story.",
+ notes: "Mounts the shipped BreadcrumbBar with fixture breadcrumb context. Covers title-only linked immediate parent, long titles, private parent, standalone task, Mine source, light/dark themes and responsive widths. Page data loading and parent authorization are outside this isolated header story.",
},
},
args: { parentTitle: "Prepare the product launch", title: "Review the release checklist" },
@@ -79,7 +76,8 @@ type Story = StoryObj;
export const Subtask: Story = {
play: async ({ canvasElement }) => {
const nav = within(await within(canvasElement).findByRole("navigation", { name: "breadcrumb" }));
- await expect(nav.getByRole("link", { name: "Prepare the product launch PAP-120" })).toHaveAttribute("href", "/PAP/issues/PAP-120");
+ await expect(nav.getByRole("link", { name: "Prepare the product launch" })).toHaveAttribute("href", "/PAP/issues/PAP-120");
+ await expect(nav.getByRole("link", { name: "Prepare the product launch" })).toHaveTextContent(/^Prepare the product launch$/);
await expect(nav.getByRole("link", { name: "Review the release checklist PAP-121" })).toHaveAttribute("aria-current", "page");
},
};
@@ -108,7 +106,8 @@ export const PrivateParent: Story = {
const nav = await within(canvasElement).findByRole("navigation", { name: "breadcrumb" });
await expect(within(nav).queryByRole("link", { name: /Prepare the product launch/ })).not.toBeInTheDocument();
await expect(within(nav).queryByRole("link", { name: /private/i })).not.toBeInTheDocument();
- await expect(within(nav).getByTestId("locked-issue-chip")).toBeVisible();
+ await expect(within(nav).getByTestId("locked-issue-chip")).toHaveTextContent(/^Private$/);
+ await expect(nav).not.toHaveTextContent("PAP-120");
await expect(nav.querySelectorAll('[aria-current="page"]')).toHaveLength(1);
},
};