Skip to content

Commit 723c58c

Browse files
authored
feat(inbox): Source product icons, PR badges, and rendering improvements (#1755)
## Problem The report list in Code lacked indication for source products and PR status. Users couldn't quickly identify what type of signal initiated a report or whether a PR already exists. Some of the spacing was rough too, and we were losing a lot of info due to truncated headings or description. <img width="900" height="208" alt="Screenshot 2026-04-21 at 18 58 42@2x" src="https://github.com/user-attachments/assets/a1f94ee1-c4c4-447b-b690-0002cea20b71" /> ## Changes <img width="968" height="198" alt="Screenshot 2026-04-21 at 17 45 08@2x" src="https://github.com/user-attachments/assets/3fa20bcf-5d99-4ee8-9ccd-4fd080d552a4" /> A few things (as summarized by Claude): - **Source product icon**: Replace generic FileTextIcon with colored source product icon (from `SOURCE_PRODUCT_META`) in report list rows, with tooltip "Report initiated by a signal from X" - **PR badge in list**: Show a mini PR badge pill in `ReportCardContent` when `implementation_pr_url` is set, with merged/closed/open color states via `usePrDetails` - **PR badge in task bar**: Move the PR badge from the detail header into the implementation task bar, showing "Create PR" / "Working on a PR…" / PR link badge depending on state - **Clean up detail header**: Remove PR badge, `implementationPrUrl` state, and `onPrUrlChange` callback from `ReportDetailPane` - **Layout polish**: Tighter list row spacing, improved summary markdown rendering (single-line, 12px font normalization) Backend companion: PostHog/posthog#55464 ## How did you test this code? [Recorded a demo.](https://posthog.slack.com/archives/C09SK2PAGKF/p1776774168687849?thread_ts=1776419606.835789&cid=C09SK2PAGKF)
1 parent 91a94a1 commit 723c58c

7 files changed

Lines changed: 239 additions & 63 deletions

File tree

apps/code/src/renderer/features/inbox/components/detail/ReportDetailPane.tsx

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
CaretRightIcon,
1616
Cloud as CloudIcon,
1717
EyeIcon,
18-
GitPullRequestIcon,
1918
LinkSimpleIcon,
2019
WarningIcon,
2120
XIcon,
@@ -54,7 +53,7 @@ import { SignalReportActionabilityBadge } from "../utils/SignalReportActionabili
5453
import { SignalReportPriorityBadge } from "../utils/SignalReportPriorityBadge";
5554
import { SignalReportStatusBadge } from "../utils/SignalReportStatusBadge";
5655
import { SignalReportSummaryMarkdown } from "../utils/SignalReportSummaryMarkdown";
57-
import { getPrNumberFromUrl, ReportTaskLogs } from "./ReportTaskLogs";
56+
import { ReportTaskLogs } from "./ReportTaskLogs";
5857
import { SignalCard } from "./SignalCard";
5958

6059
function isSuggestedReviewerRowMe(
@@ -209,9 +208,6 @@ export function ReportDetailPane({ report, onClose }: ReportDetailPaneProps) {
209208
report.actionability === "immediately_actionable" &&
210209
report.already_addressed !== true;
211210

212-
const [implementationPrUrl, setImplementationPrUrl] = useState<string | null>(
213-
null,
214-
);
215211
const [cloudPromptDraft, setCloudPromptDraft] = useState("");
216212
const cloudRepoPickerAnchorRef = useRef<HTMLDivElement>(null);
217213

@@ -279,25 +275,12 @@ export function ReportDetailPane({ report, onClose }: ReportDetailPaneProps) {
279275
<Flex align="center" gap="2" className="min-w-0">
280276
<SignalReportStatusBadge status={report.status} />
281277
<Text
282-
size="1"
283-
weight="medium"
284-
className="block min-w-0 break-words text-[13px]"
278+
size="3"
279+
weight={report.status === "ready" ? "bold" : "medium"}
280+
className="block min-w-0 text-balance break-words leading-tight"
285281
>
286282
{report.title ?? "Untitled signal"}
287283
</Text>
288-
{implementationPrUrl && (
289-
<Tooltip content={implementationPrUrl}>
290-
<a
291-
href={implementationPrUrl}
292-
target="_blank"
293-
rel="noreferrer"
294-
className="inline-flex shrink-0 items-center gap-1 rounded-full bg-green-5 px-2 py-0.5 font-medium text-[11px] text-green-12 hover:bg-green-6"
295-
>
296-
<GitPullRequestIcon size={12} weight="bold" />
297-
{getPrNumberFromUrl(implementationPrUrl) ?? "PR"}
298-
</a>
299-
</Tooltip>
300-
)}
301284
</Flex>
302285
<Flex align="center" gap="1" className="shrink-0">
303286
<Tooltip content="Copy link to this report">
@@ -555,7 +538,6 @@ export function ReportDetailPane({ report, onClose }: ReportDetailPaneProps) {
555538
onRunInCloud={
556539
canCreateImplementationPr ? handleOpenCloudConfirm : undefined
557540
}
558-
onPrUrlChange={setImplementationPrUrl}
559541
/>
560542

561543
{/* ── Cloud task confirmation dialog ────────────────────── */}

apps/code/src/renderer/features/inbox/components/detail/ReportTaskLogs.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ReportImplementationPrLink } from "@features/inbox/components/utils/ReportImplementationPrLink";
12
import { TaskLogsPanel } from "@features/task-detail/components/TaskLogsPanel";
23
import { useAuthenticatedQuery } from "@hooks/useAuthenticatedQuery";
34
import {
@@ -10,7 +11,7 @@ import {
1011
} from "@phosphor-icons/react";
1112
import { Button, Spinner, Text, Tooltip } from "@radix-ui/themes";
1213
import type { SignalReportStatus, SignalReportTask, Task } from "@shared/types";
13-
import { useEffect, useState } from "react";
14+
import { useState } from "react";
1415

1516
type Relationship = SignalReportTask["relationship"];
1617

@@ -164,11 +165,6 @@ export function getTaskPrUrl(task: Task): string | null {
164165
return null;
165166
}
166167

167-
export function getPrNumberFromUrl(prUrl: string): string | null {
168-
const match = prUrl.match(/\/pull\/(\d+)(?:$|[/?#])/);
169-
return match ? `#${match[1]}` : null;
170-
}
171-
172168
const BAR_HEIGHT = 38;
173169

174170
interface Bar {
@@ -179,22 +175,21 @@ interface Bar {
179175
tooltip?: string;
180176
/** When set, render a run-action button with this label instead of (or alongside) the status label. */
181177
runActionLabel?: string;
178+
/** PR URL produced by the implementation task, if available. */
179+
prUrl?: string | null;
182180
}
183181

184182
interface ReportTaskLogsProps {
185183
reportId: string;
186184
reportStatus: SignalReportStatus;
187185
/** Open the cloud task confirmation flow. */
188186
onRunInCloud?: () => void;
189-
/** Called when the implementation PR URL changes (or becomes null). */
190-
onPrUrlChange?: (prUrl: string | null) => void;
191187
}
192188

193189
export function ReportTaskLogs({
194190
reportId,
195191
reportStatus,
196192
onRunInCloud,
197-
onPrUrlChange,
198193
}: ReportTaskLogsProps) {
199194
const { data, isLoading } = useReportTasks(reportId, reportStatus);
200195
const [expanded, setExpanded] = useState<Relationship | null>(null);
@@ -206,9 +201,6 @@ export function ReportTaskLogs({
206201
tasks.find((t) => t.relationship === "implementation")?.task ?? null;
207202

208203
const prUrl = implementationTask ? getTaskPrUrl(implementationTask) : null;
209-
useEffect(() => {
210-
onPrUrlChange?.(prUrl);
211-
}, [prUrl, onPrUrlChange]);
212204

213205
// Build the stacked bars we'll render. We always surface the research bar
214206
// (using a pending/unavailable placeholder if no research task exists yet).
@@ -250,6 +242,7 @@ export function ReportTaskLogs({
250242
relationship: "implementation",
251243
task: implementationTask,
252244
summary: getTaskStatusSummary(implementationTask),
245+
prUrl,
253246
runActionLabel: isPendingInput ? runActionLabel : undefined,
254247
});
255248
} else if (reportStatus === "ready" || isPendingInput) {
@@ -372,9 +365,18 @@ export function ReportTaskLogs({
372365
className="flex-1 text-[11px]"
373366
style={{ color: summary.color }}
374367
>
375-
{summary.label}
368+
{bar.prUrl
369+
? summary.label
370+
: relationship === "implementation" &&
371+
(task?.latest_run?.status === "queued" ||
372+
task?.latest_run?.status === "in_progress")
373+
? "Working on a PR…"
374+
: summary.label}
376375
</Text>
377376
)}
377+
{bar.prUrl && (
378+
<ReportImplementationPrLink prUrl={bar.prUrl} size="md" />
379+
)}
378380
{showRunAction && (
379381
<Button
380382
size="1"

apps/code/src/renderer/features/inbox/components/list/ReportListRow.tsx

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
11
import { ReportCardContent } from "@features/inbox/components/utils/ReportCardContent";
2+
import { SOURCE_PRODUCT_META } from "@features/inbox/components/utils/source-product-icons";
23
import { FileTextIcon } from "@phosphor-icons/react";
3-
import { Checkbox, Flex } from "@radix-ui/themes";
4+
import { Checkbox, Flex, Tooltip } from "@radix-ui/themes";
45
import type { SignalReport } from "@shared/types";
56
import { motion } from "framer-motion";
67
import type { KeyboardEvent, MouseEvent } from "react";
78

9+
function SourceProductIcon({ sourceProducts }: { sourceProducts?: string[] }) {
10+
const firstProduct = sourceProducts?.[0];
11+
const meta = firstProduct ? SOURCE_PRODUCT_META[firstProduct] : undefined;
12+
13+
if (!meta) {
14+
return (
15+
<span className="text-gray-8">
16+
<FileTextIcon size={14} />
17+
</span>
18+
);
19+
}
20+
21+
// Always show the first (initiating) product's icon.
22+
// If later signals added more source products, list them in the tooltip.
23+
const otherLabels = (sourceProducts ?? [])
24+
.slice(1)
25+
.map((p) => SOURCE_PRODUCT_META[p]?.label)
26+
.filter(Boolean);
27+
const tooltip =
28+
otherLabels.length > 0
29+
? `Initiated by ${meta.label} · also: ${otherLabels.join(", ")}`
30+
: `Initiated by ${meta.label}`;
31+
32+
return (
33+
<Tooltip content={tooltip}>
34+
<span style={{ color: meta.color }}>
35+
<meta.Icon size={14} />
36+
</span>
37+
</Tooltip>
38+
);
39+
}
40+
841
interface ReportListRowProps {
942
report: SignalReport;
1043
isSelected: boolean;
@@ -37,11 +70,18 @@ export function ReportListRow({
3770
};
3871

3972
const rowBgClass = isSelected
40-
? "bg-gray-3"
73+
? report.is_suggested_reviewer
74+
? "bg-amber-3"
75+
: "bg-gray-3"
4176
: report.is_suggested_reviewer
4277
? "bg-amber-2"
4378
: "";
4479

80+
const hoverOverlayClass =
81+
isSelected && report.is_suggested_reviewer
82+
? "before:bg-amber-12 before:opacity-0 hover:before:opacity-[0.07]"
83+
: "before:bg-gray-12 before:opacity-0 hover:before:opacity-[0.07]";
84+
4585
return (
4686
<motion.div
4787
role="button"
@@ -69,18 +109,19 @@ export function ReportListRow({
69109
}
70110
}}
71111
className={[
72-
"relative isolate w-full cursor-pointer overflow-hidden border-gray-5 border-b py-2 pr-3 pl-2 text-left",
73-
"before:pointer-events-none before:absolute before:inset-0 before:z-[1] before:bg-gray-12 before:opacity-0 hover:before:opacity-[0.07]",
112+
"relative isolate w-full cursor-pointer overflow-hidden border-gray-5 border-b py-1.5 pr-4 pl-1.5 text-left",
113+
"before:pointer-events-none before:absolute before:inset-0 before:z-[1]",
114+
hoverOverlayClass,
74115
rowBgClass,
75116
]
76117
.filter(Boolean)
77118
.join(" ")}
78119
>
79-
<Flex align="start" gap="2" className="relative z-[2]">
120+
<Flex align="start" gap="1" className="relative z-[2]">
80121
<Flex
81122
align="center"
82123
justify="center"
83-
className="shrink-0 pt-1"
124+
className="shrink-0 pt-0.5"
84125
style={{ width: 16, minWidth: 16 }}
85126
>
86127
{showCheckbox ? (
@@ -102,13 +143,11 @@ export function ReportListRow({
102143
}
103144
/>
104145
) : (
105-
<span className="text-gray-8">
106-
<FileTextIcon size={14} />
107-
</span>
146+
<SourceProductIcon sourceProducts={report.source_products} />
108147
)}
109148
</Flex>
110149
<div className="min-w-0 flex-1">
111-
<ReportCardContent report={report} />
150+
<ReportCardContent report={report} compact />
112151
</div>
113152
</Flex>
114153
</motion.div>

apps/code/src/renderer/features/inbox/components/utils/ReportCardContent.tsx

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Badge } from "@components/ui/Badge";
2+
import { ReportImplementationPrLink } from "@features/inbox/components/utils/ReportImplementationPrLink";
23
import { SignalReportActionabilityBadge } from "@features/inbox/components/utils/SignalReportActionabilityBadge";
34
import { SignalReportPriorityBadge } from "@features/inbox/components/utils/SignalReportPriorityBadge";
45
import { SignalReportStatusBadge } from "@features/inbox/components/utils/SignalReportStatusBadge";
@@ -11,11 +12,14 @@ interface ReportCardContentProps {
1112
report: SignalReport;
1213
/** Show signal count, user count, and date in a meta row below the summary. */
1314
showMeta?: boolean;
15+
/** Tighter vertical and horizontal gaps for inbox list rows. */
16+
compact?: boolean;
1417
}
1518

1619
export function ReportCardContent({
1720
report,
1821
showMeta = false,
22+
compact = false,
1923
}: ReportCardContentProps) {
2024
const isReady = report.status === "ready";
2125

@@ -25,34 +29,59 @@ export function ReportCardContent({
2529
);
2630

2731
return (
28-
<Flex direction="column" gap="1">
29-
<Flex align="start" gapX="2" className="min-w-0">
30-
<Flex align="center" gapX="2" wrap="wrap" className="min-w-0 flex-1">
31-
<Text
32-
size="1"
33-
weight="medium"
34-
className="min-w-0 flex-1 basis-0 truncate text-[13px]"
35-
>
36-
{report.title ?? "Untitled signal"}
32+
<Flex
33+
direction="column"
34+
gap={compact ? undefined : "1"}
35+
className={compact ? "gap-0.5" : undefined}
36+
>
37+
<Flex align="start" gapX={compact ? "1" : "2"} className="min-w-0">
38+
<Text
39+
size="1"
40+
weight="medium"
41+
className="min-w-0 flex-1 break-words text-[13px] leading-tight"
42+
>
43+
{report.title ?? "Untitled signal"}
44+
</Text>
45+
{!showMeta && (
46+
<Text size="1" color="gray" className="shrink-0 text-[12px]">
47+
{updatedAtLabel}
3748
</Text>
49+
)}
50+
</Flex>
51+
52+
<Flex
53+
align="center"
54+
justify="between"
55+
gapX={compact ? "1" : "2"}
56+
className="h-[21px] w-full min-w-0" // Same height as PR badge, even if there's no PR badge
57+
>
58+
<Flex
59+
align="center"
60+
gapX={compact ? "1" : "2"}
61+
wrap="wrap"
62+
className="min-w-0 flex-1"
63+
>
3864
{!isReady && <SignalReportStatusBadge status={report.status} />}
3965
<SignalReportPriorityBadge priority={report.priority} />
4066
<SignalReportActionabilityBadge
4167
actionability={report.actionability}
4268
/>
4369
{report.is_suggested_reviewer && (
4470
<Tooltip content="You are a suggested reviewer">
45-
<Badge color="amber" style={{ height: "var(--line-height-1)" }}>
71+
<Badge
72+
color="amber"
73+
className="!leading-none inline-flex items-center justify-center"
74+
>
4675
<EyeIcon size={10} weight="bold" className="shrink-0" />
4776
</Badge>
4877
</Tooltip>
4978
)}
5079
</Flex>
51-
52-
{!showMeta && (
53-
<Text size="1" color="gray" className="shrink-0 text-[12px]">
54-
{updatedAtLabel}
55-
</Text>
80+
{report.implementation_pr_url && (
81+
<ReportImplementationPrLink
82+
prUrl={report.implementation_pr_url}
83+
skipStatusFetch={compact}
84+
/>
5685
)}
5786
</Flex>
5887

0 commit comments

Comments
 (0)