diff --git a/ui/src/components/task-chat/RichWorkProductCard.tsx b/ui/src/components/task-chat/RichWorkProductCard.tsx
index 46581ded661..4b5dedf7226 100644
--- a/ui/src/components/task-chat/RichWorkProductCard.tsx
+++ b/ui/src/components/task-chat/RichWorkProductCard.tsx
@@ -10,6 +10,7 @@ import { isImageLikeOutput, isVideoLikeOutput } from "@/lib/issue-output";
import { attachmentDownloadPath } from "@/lib/issue-attachments";
import type { IssueWorkProduct } from "@paperclipai/shared";
import {
+ ChevronDown,
ExternalLink,
Maximize2,
File,
@@ -23,7 +24,7 @@ import {
} from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { GithubIcon } from "@/components/icons/github-icon";
-import { cn } from "@/lib/utils";
+import { cn, formatDateTime } from "@/lib/utils";
type StateChip = {
label: string;
@@ -135,6 +136,7 @@ export interface RichWorkProductCardProps {
export function RichWorkProductCard({ workProduct, href, variant = "card" }: RichWorkProductCardProps) {
const openIssueGallery = useContext(IssueGalleryContext);
const [galleryOpen, setGalleryOpen] = useState(false);
+ const [detailsOpen, setDetailsOpen] = useState(false);
const metadata = workProduct.metadata;
const contentType = stringMeta(metadata, "contentType") ?? "";
const isImage = isImageLikeOutput(contentType, stringMeta(metadata, "originalFilename") ?? workProduct.title);
@@ -161,7 +163,7 @@ export function RichWorkProductCard({ workProduct, href, variant = "card" }: Ric
break;
case "branch":
Icon = GitBranch;
- meta = [stringMeta(metadata, "repository", "repo", "repositoryName"), stringMeta(metadata, "branch", "branchName") ?? workProduct.externalId, urlLabel(workProduct.url)];
+ meta = [href ? "Branch" : "Branch · no remote link", stringMeta(metadata, "repository", "repo", "repositoryName"), stringMeta(metadata, "branch", "branchName") ?? workProduct.externalId, urlLabel(workProduct.url)];
action = "Open on GitHub";
break;
case "artifact": {
@@ -232,6 +234,9 @@ export function RichWorkProductCard({ workProduct, href, variant = "card" }: Ric
? attachmentDownloadPath({ contentPath: artifactContentPath })
: href)
: href;
+ const expandable = !compact && !mediaPath && !actionHref;
+ const summary = workProduct.summary?.trim();
+ const linklessBranch = workProduct.type === "branch" && !href;
const openGallery = () => {
if (mediaPath && !openIssueGallery?.(mediaPath)) setGalleryOpen(true);
};
@@ -272,9 +277,22 @@ export function RichWorkProductCard({ workProduct, href, variant = "card" }: Ric
)}
-
{workProduct.title}
+
{workProduct.title}
{visibleMeta.length > 0 ?
{visibleMeta.join(" · ")}
: null}
{statsLabel ?
{statsLabel}
: null}
+ {!compact && summary && !linklessBranch ?
{summary}
: null}
+ {expandable && detailsOpen ? (
+
+ {workProduct.type !== "branch" ?
No link was provided for this work product.
: null}
+
{workProduct.provider} · {workProduct.status.replaceAll("_", " ")} · Updated {formatDateTime(workProduct.updatedAt)}
+ {linklessBranch && summary ? (
+
+ Saved description
+ {summary}
+
+ ) : null}
+
+ ) : null}
{chip ?
: null}
@@ -296,6 +314,17 @@ export function RichWorkProductCard({ workProduct, href, variant = "card" }: Ric
{compact ? null : {action}}
+ ) : expandable ? (
+
) : null}
{galleryOpen && mediaPath ? (
diff --git a/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx b/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx
index 00961fdbaad..c06903a59f0 100644
--- a/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx
+++ b/ui/src/components/task-chat/TaskChatProtocolCard.test.tsx
@@ -206,6 +206,43 @@ describe("TaskChatProtocolCard", () => {
expect(document.querySelector('[role="dialog"]')).toBeNull();
});
+ it("shows a branch summary and lets users inspect a work product without a remote URL", () => {
+ flushSync(() => root.render(
+ ,
+ ));
+ expect(container.textContent).not.toContain("Uncommitted implementation changes");
+ expect(container.textContent).toContain("Branch · no remote link");
+ expect(container.querySelector("strong")?.className).toContain("line-clamp-2");
+ expect(container.querySelector("a")).toBeNull();
+ const button = container.querySelector('button[aria-label^="Show details:"]');
+ expect(button?.getAttribute("aria-expanded")).toBe("false");
+ flushSync(() => button!.click());
+ const savedDescription = container.querySelector("details");
+ expect(savedDescription?.open).toBe(false);
+ expect(savedDescription?.textContent).toContain("Uncommitted implementation changes");
+ flushSync(() => savedDescription!.querySelector("summary")!.click());
+ expect(savedDescription?.open).toBe(true);
+ flushSync(() => savedDescription!.querySelector("summary")!.click());
+ expect(container.textContent).toContain("git · active · Updated");
+ expect(container.querySelector('button[aria-label^="Hide details:"]')?.getAttribute("aria-expanded")).toBe("true");
+ expect(container.querySelector("strong")?.className).not.toContain("line-clamp-2");
+ flushSync(() => container.querySelector('button[aria-label^="Hide details:"]')!.click());
+ expect(container.querySelector("details")).toBeNull();
+ expect(container.textContent).toContain("Branch · no remote link");
+ expect(container.querySelector('button[aria-label^="Show details:"]')?.getAttribute("aria-expanded")).toBe("false");
+ });
+
it.each(["text/html", "application/zip"])("labels %s artifact links as downloads", (contentType) => {
const contentPath = "/api/attachments/file/content";
flushSync(() => root.render(
diff --git a/ui/storybook/stories/rich-work-product-cards.stories.tsx b/ui/storybook/stories/rich-work-product-cards.stories.tsx
index 3b4e91f6ae7..a85c39c499e 100644
--- a/ui/storybook/stories/rich-work-product-cards.stories.tsx
+++ b/ui/storybook/stories/rich-work-product-cards.stories.tsx
@@ -303,3 +303,41 @@ export const MessageTailMediaAndTypedChips: Story = {
),
};
+
+const LOCAL_BRANCH: IssueWorkProduct = {
+ ...product(KINDS[2], STATES[0]),
+ id: "local-branch-no-remote",
+ title: "Company skill update entrypoint",
+ provider: "git",
+ updatedAt: new Date("2026-10-03T11:52:54.448Z"),
+ url: null,
+ metadata: null,
+ status: "active",
+ summary: "Uncommitted implementation changes are in the execution working tree on branch company-skill-update-entrypoint. Includes a guarded skill API, runner tool, docs, and focused regressions. No commit or push was requested.",
+};
+
+/** A work product without a remote URL still explains what it is and opens its own details. */
+export const BranchWithoutRemoteLink: Story = {
+ args: { workProduct: LOCAL_BRANCH, href: null },
+ render: (args) => (
+
+
+
+ ),
+ play: async ({ canvasElement }) => {
+ expect(canvasElement.textContent).toContain("Branch · no remote link");
+ expect(canvasElement.textContent).not.toContain("Uncommitted implementation changes");
+ expect(canvasElement.querySelector('button[aria-label^="Show details:"]')).not.toBeNull();
+ },
+};
+
+export const BranchDetailsExpanded: Story = {
+ ...BranchWithoutRemoteLink,
+ play: async ({ canvasElement, userEvent }) => {
+ const button = canvasElement.querySelector('button[aria-label^="Show details:"]');
+ await userEvent.click(button!);
+ expect(canvasElement.textContent).toContain("git · active · Updated");
+ expect(canvasElement.textContent).toContain("Saved description");
+ expect(canvasElement.querySelector("details")?.open).toBe(false);
+ },
+};