diff --git a/ui/src/components/IssueReferenceActivitySummary.tsx b/ui/src/components/IssueReferenceActivitySummary.tsx index 153db735da3..062985c5348 100644 --- a/ui/src/components/IssueReferenceActivitySummary.tsx +++ b/ui/src/components/IssueReferenceActivitySummary.tsx @@ -1,4 +1,5 @@ import type { ActivityEvent } from "@paperclipai/shared"; +import { Plus, Minus } from "lucide-react"; import { IssueReferencePill } from "./IssueReferencePill"; type ActivityIssueReference = { @@ -13,14 +14,31 @@ function readIssueReferences(details: Record | null | undefined return value.filter((item): item is ActivityIssueReference => !!item && typeof item === "object"); } -function Section({ label, items }: { label: string; items: ActivityIssueReference[] }) { +function Section({ + label, + icon, + items, + strikethrough, +}: { + label: string; + icon: React.ReactNode; + items: ActivityIssueReference[]; + strikethrough?: boolean; +}) { if (items.length === 0) return null; return (
- {label} + + {icon} + {label} + {items.map((issue) => ( -
-
+
); } diff --git a/ui/src/components/IssueReferencePill.tsx b/ui/src/components/IssueReferencePill.tsx index b2a74728788..cf3d269743e 100644 --- a/ui/src/components/IssueReferencePill.tsx +++ b/ui/src/components/IssueReferencePill.tsx @@ -1,12 +1,16 @@ import type { IssueRelationIssueSummary } from "@paperclipai/shared"; import { Link } from "@/lib/router"; import { cn } from "../lib/utils"; +import { StatusIcon } from "./StatusIcon"; export function IssueReferencePill({ issue, + strikethrough, className, }: { - issue: Pick; + issue: Pick & + Partial>; + strikethrough?: boolean; className?: string; }) { const issueLabel = issue.identifier ?? issue.id; @@ -14,14 +18,22 @@ export function IssueReferencePill({ return ( - + {issue.status ? ( + + ) : ( + + )} {issue.identifier ?? issue.title} ); diff --git a/ui/src/components/IssueRelatedWorkPanel.test.tsx b/ui/src/components/IssueRelatedWorkPanel.test.tsx index f2ca0493dee..d1bd505f3ed 100644 --- a/ui/src/components/IssueRelatedWorkPanel.test.tsx +++ b/ui/src/components/IssueRelatedWorkPanel.test.tsx @@ -60,4 +60,37 @@ describe("IssueRelatedWorkPanel", () => { expect(html).toContain("plan"); expect(html).toContain("comment"); }); + + it("collapses duplicate source labels into a single chip with a count", () => { + const html = renderToStaticMarkup( + , + ); + + const commentMatches = html.match(/>comment(); + for (const source of sources) { + const existing = groups.get(source.label); + if (existing) { + existing.count += 1; + } else { + groups.set(source.label, { + label: source.label, + count: 1, + sampleMatchedText: source.matchedText ?? null, + }); + } + } + return Array.from(groups.values()); +} + function Section({ title, description, @@ -15,39 +38,46 @@ function Section({ return (
-

{title}

+

{title}

{description}

{items.length === 0 ? (

{emptyLabel}

) : ( -
- {items.map((item) => ( -
-
+
    + {items.map((item) => { + const groupedSources = groupSourcesByLabel(item.sources); + const showTitle = item.issue.identifier !== item.issue.title; + return ( +
  • - {item.issue.identifier !== item.issue.title ? ( - {item.issue.title} - ) : null} - - {item.mentionCount} source{item.mentionCount === 1 ? "" : "s"} - -
-
- {item.sources.map((source) => ( - - {source.label} + {showTitle ? ( + + {item.issue.title} - ))} -
-
- ))} -
+ ) : null} +
+ {groupedSources.map((group) => ( + + {group.label} + {group.count > 1 ? ( + ×{group.count} + ) : null} + + ))} +
+ + ); + })} + )}
); diff --git a/ui/src/pages/DesignGuide.tsx b/ui/src/pages/DesignGuide.tsx index 9d4ab867f4d..656fcead050 100644 --- a/ui/src/pages/DesignGuide.tsx +++ b/ui/src/pages/DesignGuide.tsx @@ -123,6 +123,7 @@ import { FilterBar, type FilterValue } from "@/components/FilterBar"; import { InlineEditor } from "@/components/InlineEditor"; import { PageSkeleton } from "@/components/PageSkeleton"; import { Identity } from "@/components/Identity"; +import { IssueReferencePill } from "@/components/IssueReferencePill"; /* ------------------------------------------------------------------ */ /* Section wrapper */ @@ -466,6 +467,21 @@ export function DesignGuide() { ))} + + +

+ Used wherever a task is referenced — in markdown, the Related Work tab, and activity summaries. + Pass status to show the target issue's state at a glance. + Use strikethrough for "removed" contexts. +

+
+ + + + + +
+
{/* ============================================================ */}