diff --git a/ui/src/components/IssueProperties.test.tsx b/ui/src/components/IssueProperties.test.tsx
index c6006169f60..d11059a7a23 100644
--- a/ui/src/components/IssueProperties.test.tsx
+++ b/ui/src/components/IssueProperties.test.tsx
@@ -1814,7 +1814,7 @@ describe("IssueProperties", () => {
displayKey: null,
iconKey: "github",
statusCategory: "succeeded",
- statusIconKey: "git-merge",
+ statusIconKey: null,
statusLabel: "Merged",
liveness: "fresh",
displayTitle: "acme/web#241: Add rich object presentation metadata",
@@ -1868,6 +1868,9 @@ describe("IssueProperties", () => {
expect(pullRequestLink?.textContent).not.toContain("acme/web#241");
expect(pullRequestLink?.textContent).not.toContain("Github Pull Request");
expect(pullRequestLink?.querySelectorAll("svg")).toHaveLength(1);
+ expect(pullRequestLink?.className).not.toContain("paperclip-mention-chip");
+ expect(pullRequestLink?.className).not.toContain("rounded-full");
+ expect(pullRequestLink?.className).not.toContain("border");
act(() => root.unmount());
});
diff --git a/ui/src/components/IssueProperties.tsx b/ui/src/components/IssueProperties.tsx
index 9ee63246a43..7bd5d8ca831 100644
--- a/ui/src/components/IssueProperties.tsx
+++ b/ui/src/components/IssueProperties.tsx
@@ -35,14 +35,19 @@ import { PriorityIcon } from "./PriorityIcon";
import { Identity } from "./Identity";
import { IssueReferencePill } from "./IssueReferencePill";
import { formatDate, formatDateTime, cn, projectUrl } from "../lib/utils";
-import { ExternalObjectPill } from "./ExternalObjectPill";
+import { ExternalObjectStatusIcon } from "./ExternalObjectStatusIcon";
import type { IssueExternalObjectGroup } from "../hooks/useIssueExternalObjects";
import {
+ externalObjectCategoryLabel,
externalObjectIconForKey,
externalObjectProviderLabel,
externalObjectToneSeverity,
externalObjectTypeLabel,
} from "../lib/external-objects";
+import {
+ externalObjectStatusIcon,
+ externalObjectStatusIconDefault,
+} from "../lib/status-colors";
import { timeAgo } from "../lib/timeAgo";
import { Button } from "@/components/ui/button";
import { ToggleSwitch } from "@/components/ui/toggle-switch";
@@ -218,6 +223,116 @@ function externalObjectRowLabel(group: IssueExternalObjectGroup): React.ReactNod
);
}
+function githubObjectPropertyValue(url: string | null | undefined): string | null {
+ if (!url) return null;
+ try {
+ const parsed = new URL(url);
+ if (parsed.hostname !== "github.com") return null;
+ const [, owner, repo, kind, number] = parsed.pathname.split("/");
+ if (!owner || !repo || !number) return null;
+ if (kind === "pull") return `PR ${number}`;
+ if (kind === "issues") return `Issue ${number}`;
+ return null;
+ } catch {
+ return null;
+ }
+}
+
+function externalObjectPropertyStatusLabel(group: IssueExternalObjectGroup): string {
+ return group.pill.statusLabel ?? externalObjectCategoryLabel(group.pill.statusCategory);
+}
+
+function externalObjectPropertyValue(group: IssueExternalObjectGroup): string {
+ const { pill } = group;
+ const statusLabel = externalObjectPropertyStatusLabel(group);
+ const githubLabel = pill.providerKey === "github" ? githubObjectPropertyValue(pill.url) : null;
+ const base = githubLabel ?? pill.displayTitle?.trim() ?? externalObjectRowDisplayKey(group);
+ return statusLabel ? `${base} - ${statusLabel}` : base;
+}
+
+function isMergedExternalObject(group: IssueExternalObjectGroup): boolean {
+ const statusLabel = externalObjectPropertyStatusLabel(group);
+ return group.pill.statusIconKey === "git-merge" || statusLabel.toLowerCase() === "merged";
+}
+
+function externalObjectPropertyTone(group: IssueExternalObjectGroup): string {
+ if (isMergedExternalObject(group)) {
+ return "text-violet-600 dark:text-violet-400";
+ }
+ const tone = externalObjectStatusIcon[group.pill.statusCategory] ?? externalObjectStatusIconDefault;
+ return tone.split(" ").filter((c) => c.startsWith("text-")).join(" ");
+}
+
+function externalObjectPropertyStatusIconKey(group: IssueExternalObjectGroup): string | null | undefined {
+ if (isMergedExternalObject(group)) return group.pill.statusIconKey ?? "git-merge";
+ return group.pill.statusIconKey;
+}
+
+function externalObjectPropertyTitle(group: IssueExternalObjectGroup): string {
+ const { pill, sourceLabels } = group;
+ const base = pill.displayTitle ?? externalObjectPropertyValue(group);
+ return sourceLabels.length > 0 ? `${base} - ${sourceLabels.join(", ")}` : base;
+}
+
+function ExternalObjectPropertyValue({ group }: { group: IssueExternalObjectGroup }) {
+ const { pill, mentionCount } = group;
+ const statusLabel = externalObjectPropertyStatusLabel(group);
+ const providerLabel = externalObjectProviderLabel(pill.providerKey);
+ const typeLabel = externalObjectTypeLabel(pill.objectType);
+ const value = externalObjectPropertyValue(group);
+ const content = (
+ <>
+
+ {value}
+ {mentionCount > 1 ? (
+ x{mentionCount}
+ ) : null}
+ >
+ );
+ const className = cn(
+ "inline-flex min-w-0 max-w-full items-center gap-1 text-xs font-medium no-underline",
+ externalObjectPropertyTone(group),
+ pill.url ? "hover:underline focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring" : "",
+ );
+
+ if (pill.url) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return (
+
+ {content}
+
+ );
+}
+
const ISSUE_THINKING_EFFORT_OPTIONS = {
claude_local: [
{ value: "", label: "Default" },
@@ -2249,19 +2364,14 @@ export function IssueProperties({
<>
{sortExternalObjectGroups(externalObjects)
.map((externalObject) => {
- const { pill, mentionCount, sourceLabels, group } = externalObject;
+ const { pill, group } = externalObject;
return (
-
+
);
})}