feat(lens-ui): show findings ranked by priority with frequency and highlighted evidence (#45143)

* feat(lens-ui): compute how often a finding hits sampled traces per day

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): copy a finding for an agent as markdown

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): add affected, unaffected and quote highlight color tokens

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): add a frequency card with stacked affected traces per day

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(lens-ui): keep the issue brief title out of the page heading outline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): lay out a finding as summary, fix, frequency and highlighted examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): show findings as a dated list with percent affected beside the open finding

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): cover frequency and highlighted quotes on a finding

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): follow findings into the split list and example cards

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(lens-ui): take finding chart and quote colors from the dashboard theme

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): add a shared priority dot and pill for findings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(lens-ui): soften the frequency card and show its date range

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(lens-ui): rank findings under high, medium and low priority headings

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* style(lens-ui): show finding priority, label quotes by content and collapse extra examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): prove findings are grouped and ordered by priority

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(lens-ui): cover finding priority, quote labels and example collapsing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ishaan-berri 2026-10-07 12:57:04 -07:00 • committed by GitHub
parent a9b9700790
commit 7921716f39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 845 additions and 249 deletions

View file

@ -211,6 +211,10 @@
--trace-row-hover: oklch(0.975 0.008 215);
--trace-row-selected: oklch(0.95 0.035 200);
--trace-brand: oklch(0.6 0.13 195);
--finding-affected: var(--info);
--finding-unaffected: oklch(0.551 0.027 264.364 / 0.45);
--finding-quote: color-mix(in oklab, var(--warning) 16%, transparent);
--finding-ring: 0 0 0 1px oklch(0 0 0 / 0.06), 0 1px 2px -1px oklch(0 0 0 / 0.06), 0 2px 4px 0 oklch(0 0 0 / 0.04);
--trace-border: oklch(0.92 0.01 230);
--trace-line: oklch(0.88 0.03 205);
--trace-card-border: oklch(0.93 0.01 230);
@ -288,6 +292,10 @@
--trace-row-hover: oklch(0.23 0.018 230);
--trace-row-selected: oklch(0.29 0.05 210);
--trace-brand: oklch(0.78 0.13 190);
--finding-affected: var(--info);
--finding-unaffected: oklch(0.707 0.022 261.325 / 0.35);
--finding-quote: color-mix(in oklab, var(--warning) 24%, transparent);
--finding-ring: 0 0 0 1px oklch(1 0 0 / 0.08);
--trace-border: oklch(0.3 0.02 235);
--trace-line: oklch(0.36 0.04 210);
--trace-card-border: oklch(0.27 0.02 235);
@ -326,6 +334,10 @@
--color-trace-row-hover: var(--trace-row-hover);
--color-trace-row-selected: var(--trace-row-selected);
--color-trace-brand: var(--trace-brand);
--color-finding-affected: var(--finding-affected);
--color-finding-unaffected: var(--finding-unaffected);
--color-finding-quote: var(--finding-quote);
--shadow-finding-ring: var(--finding-ring);
--color-trace-border: var(--trace-border);
--color-trace-line: var(--trace-line);
--color-trace-card-border: var(--trace-card-border);

View file

@ -141,9 +141,7 @@ describe("Lens interactive demo", () => {
await user.click(await screen.findByRole("row", { name: /Repeated lookups leave customers without an answer/ }));
const finding = screen.getByRole("complementary", { name: "Finding details" });
expect(within(finding).getByText(/The support agent retries/)).toBeVisible();
const summaries = within(finding).getAllByText("support_agent", { exact: true });
await user.click(summaries[0]);
await user.click(within(finding).getAllByRole("button", { name: /Open original step/ })[0]);
await user.click(within(finding).getAllByRole("button", { name: "View span" })[0]);
expect(await screen.findByRole("complementary", { name: "Span details" })).toHaveTextContent(
"I will check that for you.",
);
@ -157,7 +155,8 @@ describe("Lens interactive demo", () => {
await user.click(within(finding).getByRole("button", { name: "Back to finding" }));
expect(within(finding).getByText(/The support agent retries/)).toBeVisible();
await user.click(within(finding).getByRole("button", { name: "Close finding (Esc)" }));
expect(await screen.findByRole("table", { name: "Findings" })).toBeVisible();
expect(await screen.findByRole("grid", { name: "Findings" })).toBeVisible();
expect(screen.queryByRole("complementary", { name: "Finding details" })).not.toBeInTheDocument();
expect(network).not.toHaveBeenCalled();
await expectUrl(onUrlUpdate, (url) => expect(url.get("demo")).toBe("true"));
await expectUrl(onUrlUpdate, (url) => expect(url.has("span")).toBe(false));

View file

@ -116,8 +116,7 @@ it("stacks a quote's original step over the finding and keeps the feedback draft
const panel = screen.getByRole("complementary", { name: "Finding details" });
const reason = () => within(panel).getByRole("textbox", { name: "What should Lens remember?", hidden: true });
fireEvent.change(reason(), { target: { value: "Draft feedback" } });
for (const summary of within(panel).getAllByText(/quote$/)) await user.click(summary);
await user.click(within(panel).getAllByRole("button", { name: "Open original step" })[0]);
await user.click(within(panel).getAllByRole("button", { name: "View span" })[0]);
expect(await within(panel).findByTestId("run-view")).toHaveTextContent("trace-1 at step-a");
expect(reason()).not.toBeVisible();
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
@ -127,13 +126,51 @@ it("stacks a quote's original step over the finding and keeps the feedback draft
expect(reason()).toBeVisible();
expect(reason()).toHaveValue("Draft feedback");
await user.click(within(panel).getAllByRole("button", { name: "Open original step" })[1]);
await user.click(within(panel).getAllByRole("button", { name: "View span" })[1]);
expect(await within(panel).findByTestId("run-view")).toHaveTextContent("trace-2 at step-b");
const url = new URLSearchParams(String(onUrlUpdate.mock.lastCall?.[0].queryString ?? ""));
expect(url.get("evidence")).toBe(traceOf("trace-2"));
expect(url.get("evidence_span")).toBe("step-b");
});
it("reports how many sampled traces the finding affected and highlights each quoted line", () => {
const traceOf = (id: string) => btoa(JSON.stringify(["traces", "", id]));
const sampled = ["a", "b", "c", "d"].map((id) => ({
id: traceOf(id),
name: `run ${id}`,
start_time: "2026-10-01T10:00:00Z",
metadata: [],
root_seen: true,
service: "support_agent",
source: "traces" as const,
span_count: 1,
team_id: "",
trace_id: id,
trace_ref: "",
}));
const current: Finding = {
...finding,
occurrences: [traceOf("a")],
evidence: [{ execution_id: traceOf("a"), span_id: "s", quote: "files:read is missing", role: "support" }],
};
renderWithLens(
<Inspector.Root
items={[]}
itemKey={ownedFindingKey}
selected={owned(current)}
onSelectedChange={vi.fn()}
noun="finding"
storageKey="test.finding"
>
<FindingPanel readOnly busy={false} sampledRuns={sampled} onReview={vi.fn()} />
</Inspector.Root>,
);
expect(screen.getByRole("region", { name: "Frequency" })).toHaveTextContent(/25%\s*1 of 4 traces affected/);
const example = screen.getByRole("article", { name: "run a" });
expect(within(example).getByText("files:read is missing").tagName).toBe("MARK");
expect(screen.queryByRole("article", { name: "run b" })).not.toBeInTheDocument();
});
it("shows contributing investigation runs and every affected trace, including older traces without retained quotes", async () => {
const traceId = btoa(JSON.stringify(["traces", "", "older-trace", ""]));
const current: Finding = {
@ -142,8 +179,33 @@ it("shows contributing investigation runs and every affected trace, including ol
investigation_runs: ["first-investigation-run", "second-investigation-run"],
};
renderWithLens(<Harness current={current} onReview={vi.fn()} />);
expect(screen.getByText("Found across 2 investigation runs")).toBeInTheDocument();
expect(screen.getByText(/1 affected trace/)).toBeInTheDocument();
fireEvent.click(screen.getByText("older-trace"));
expect(screen.getByRole("button", { name: "Open original trace" })).toBeInTheDocument();
expect(screen.getByText("1 affected trace")).toBeVisible();
expect(screen.getByText("Found across 2 investigation runs")).toBeVisible();
const example = screen.getByRole("article", { name: "Trace older-tr" });
expect(within(example).getByText("No quote was retained for this trace.")).toBeVisible();
expect(within(example).getByRole("button", { name: "View trace" })).toBeVisible();
});
it("shows the finding's priority and keeps the first three examples, revealing the rest on request", async () => {
const user = userEvent.setup();
const traceOf = (id: string) => btoa(JSON.stringify(["traces", "", id]));
const ids = ["t1", "t2", "t3", "t4", "t5"];
const current: Finding = {
...finding,
occurrences: ids.map(traceOf),
evidence: ids.map((id) => ({
execution_id: traceOf(id),
span_id: id,
quote: `Input: ${id}\nOutput: done`,
role: "support" as const,
})),
};
renderWithLens(<Harness current={current} onReview={vi.fn()} />);
const panel = screen.getByRole("complementary", { name: "Finding details" });
expect(within(panel).getByText("High priority")).toBeVisible();
expect(within(panel).getAllByRole("article")).toHaveLength(3);
expect(within(panel).getAllByText("Call and result")).toHaveLength(3);
await user.click(within(panel).getByRole("button", { name: "Show 2 more examples" }));
expect(within(panel).getAllByRole("article")).toHaveLength(5);
expect(within(panel).queryByRole("button", { name: /Show \d+ more/ })).not.toBeInTheDocument();
});

View file

@ -1,23 +1,32 @@
"use client";
import { useState } from "react";
import { ArrowUpRight } from "lucide-react";
import { ChevronRight, ClipboardCopy, X } from "lucide-react";
import { Inspector } from "@/components/shared/Inspector";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { useNow } from "@/hooks/useNow";
import { copyToClipboard } from "@/utils/dataUtils";
import { AddToDatasetButton } from "../datasets/AddToDatasetDialog";
import { evidenceTarget } from "../model/findings";
import { runTime } from "../model/format";
import { evidenceTarget, findingMarkdown } from "../model/findings";
import { findingFrequency } from "../model/frequency";
import { agoLabel, runTime } from "../model/format";
import { findingAgents, findingKey, type OwnedFinding, sampledExecutions } from "../model/inbox";
import type { Finding, Sample } from "../model/types";
import { EvidenceView } from "./Evidence";
import { FrequencyCard } from "./FrequencyCard";
import { IssueBrief } from "./IssueBrief";
import { PriorityPill } from "./PriorityMark";
import { type EvidenceRef, useEvidenceRoute } from "../route";
export const ownedFindingKey = (owned: OwnedFinding): string => findingKey(owned.lens, owned.finding);
type Quote = Finding["evidence"][number];
const SECTION_LABEL = "text-xs font-medium text-muted-foreground";
export interface FindingDetailsProps {
readonly finding: Finding;
readonly lensId?: string;
@ -27,6 +36,240 @@ export interface FindingDetailsProps {
readonly busy: boolean;
readonly onOpenEvidence: (evidence: EvidenceRef) => void;
readonly onReview: (status: Finding["status"], reason: string) => void;
readonly onClose?: () => void;
}
function TopBar({ finding, onClose }: Pick<FindingDetailsProps, "finding" | "onClose">) {
const now = useNow(30000);
return (
<div className="sticky top-0 z-raised flex h-11 items-center justify-between gap-2 bg-background/95 px-4 backdrop-blur">
<p className="flex min-w-0 items-center gap-2 font-mono text-xs text-muted-foreground">
<span className="truncate" title={finding.id}>
{finding.id.slice(0, 8)}
</span>
<span aria-hidden="true">·</span>
<span className="whitespace-nowrap tabular-nums" title={runTime(finding.last_seen)}>
{agoLabel(Date.parse(finding.last_seen), now)}
</span>
</p>
<div className="flex shrink-0 gap-1.5">
<Button
variant="outline"
size="xs"
className="enabled:active:scale-[0.96]"
onClick={() => void copyToClipboard(findingMarkdown(finding), "Copied for agent")}
>
<ClipboardCopy />
Copy for agent
</Button>
{onClose && (
<Button
variant="ghost"
size="xs"
className="enabled:active:scale-[0.96]"
aria-label="Close finding (Esc)"
onClick={onClose}
>
<X />
Close
</Button>
)}
</div>
</div>
);
}
function Disclosure({ title, children }: { title: string; children: React.ReactNode }) {
return (
<details className="group rounded-lg">
<summary className="flex cursor-pointer list-none items-center gap-1.5 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<ChevronRight
aria-hidden="true"
className="size-4 text-muted-foreground transition-[rotate] duration-150 group-open:rotate-90 motion-reduce:transition-none"
/>
{title}
</summary>
<div className="mt-3">{children}</div>
</details>
);
}
function ProseSection({ title, children }: { title: string; children: string }) {
return (
<section>
<h2 className="mb-1.5 text-sm font-semibold">{title}</h2>
<p className="max-w-[70ch] text-sm leading-relaxed text-pretty whitespace-pre-wrap text-foreground/85">
{children}
</p>
</section>
);
}
const FIELD = /^(Input|Output|Status|Error)\s*:/gm;
const FIELD_LABEL: Readonly<Record<string, string>> = {
"Input,Output": "Call and result",
Input: "Call input",
Output: "Returned output",
Status: "Span status",
Error: "Error",
};
function quoteLabel(quote: Quote, isTrace: boolean): string {
if (quote.role === "counterexample") return "Counterexample";
const fields = [...new Set(Array.from(quote.quote.matchAll(FIELD), (m) => m[1]))].join(",");
return FIELD_LABEL[fields] ?? (isTrace ? "Trace step" : "Logged request");
}
const MARK = {
support: "rounded-sm bg-finding-quote px-0.5 text-inherit",
counterexample: "rounded-sm bg-success/20 px-0.5 text-inherit",
} as const;
function QuoteCard({ quote, onOpen }: { quote: Quote; onOpen: () => void }) {
const isTrace = evidenceTarget(quote.execution_id)?.source === "traces";
return (
<div className="-mx-1 rounded-lg border border-transparent bg-background p-2.5 shadow-finding-ring">
<div className="mb-1.5 flex items-center justify-between gap-2">
<span className="min-w-0 truncate text-sm font-medium">{quoteLabel(quote, isTrace)}</span>
<Button variant="outline" size="xs" className="enabled:active:scale-[0.96]" onClick={onOpen}>
{isTrace ? "View span" : "View request"}
</Button>
</div>
<button
type="button"
tabIndex={-1}
title="Open this evidence"
onClick={onOpen}
className="block w-full overflow-x-auto rounded-md bg-muted/60 py-1.5 text-left font-mono text-xs leading-5 transition-[background-color] duration-150 hover:bg-muted"
>
<code className="block border-l-2 border-warning/40 px-2.5 break-words whitespace-pre-wrap text-foreground/85">
<mark className={MARK[quote.role]}>{quote.quote}</mark>
</code>
</button>
</div>
);
}
function EvidenceRail({ children }: { children: React.ReactNode }) {
return (
<>
<div className="relative flex gap-2">
<div className="relative flex w-3 shrink-0 flex-col items-center">
<div className="flex h-5 w-full items-center justify-center">
<div className="size-1 rounded-full bg-border" />
</div>
<div aria-hidden="true" className="h-2 w-px bg-border" />
</div>
<div className="min-w-0 flex-1 text-xs leading-5 font-medium text-muted-foreground">Evidence</div>
</div>
<div className="relative">
<div aria-hidden="true" className="absolute inset-y-0 left-[5px] w-px bg-border" />
<div className="relative z-raised space-y-3">{children}</div>
</div>
</>
);
}
interface ExampleGroup {
readonly id: string;
readonly run: Sample["executions"][number] | undefined;
readonly quotes: readonly Quote[];
}
function Example({ group, onOpenEvidence }: { group: ExampleGroup; onOpenEvidence: (e: EvidenceRef) => void }) {
const traceId = evidenceTarget(group.id)?.id;
const name = group.run?.name ?? (traceId ? `Trace ${traceId.slice(0, 8)}` : "Recorded run");
return (
<article aria-label={name} className="flex w-full flex-col items-start gap-3 rounded-xl bg-muted/50 p-3.5">
<div className="flex w-full items-baseline justify-between gap-3">
<h3 className="min-w-0 truncate text-sm font-medium" title={traceId}>
{name}
</h3>
<span className="shrink-0 text-xs text-muted-foreground tabular-nums">
{[group.run?.service, group.run && runTime(group.run.start_time)].filter(Boolean).join(" · ")}
</span>
</div>
<div className="w-full">
{group.quotes.length === 0 ? (
<div className="flex items-center justify-between gap-3">
<p className="text-sm text-muted-foreground">No quote was retained for this trace.</p>
<Button variant="outline" size="xs" onClick={() => onOpenEvidence({ id: group.id, span: "" })}>
View trace
</Button>
</div>
) : (
<EvidenceRail>
{group.quotes.map((quote, i) => (
<QuoteCard
key={`${quote.span_id}-${i}`}
quote={quote}
onOpen={() => onOpenEvidence({ id: quote.execution_id, span: quote.span_id })}
/>
))}
</EvidenceRail>
)}
</div>
</article>
);
}
const VISIBLE_EXAMPLES = 3;
function Examples({
groups,
onOpenEvidence,
}: {
groups: readonly ExampleGroup[];
onOpenEvidence: (e: EvidenceRef) => void;
}) {
const [expanded, setExpanded] = useState(false);
if (groups.length === 0) return <p className="text-sm text-muted-foreground">No examples were recorded.</p>;
const shown = expanded ? groups : groups.slice(0, VISIBLE_EXAMPLES);
const hidden = groups.length - shown.length;
return (
<>
{shown.map((group) => (
<Example key={group.id} group={group} onOpenEvidence={onOpenEvidence} />
))}
{hidden > 0 && (
<Button
variant="ghost"
size="sm"
className="self-start text-muted-foreground"
onClick={() => setExpanded(true)}
>
Show {hidden} more {hidden === 1 ? "example" : "examples"}
</Button>
)}
</>
);
}
function ReviewForm({ finding, busy, onReview }: Pick<FindingDetailsProps, "finding" | "busy" | "onReview">) {
const [reason, setReason] = useState(finding.reason ?? "");
return (
<section className="space-y-3 border-t pt-6">
<label className="grid gap-2 text-sm font-medium">
What should Lens remember?
<Textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="What should Lens know about this finding?"
/>
</label>
<p className="text-xs text-muted-foreground">Your explanation informs future scans of this Lens.</p>
<div className="flex flex-wrap gap-2">
{finding.kind === "issue" && (
<Button disabled={busy} onClick={() => onReview(finding.status === "resolved" ? "open" : "resolved", reason)}>
{finding.status === "resolved" ? "Reopen" : "Mark resolved"}
</Button>
)}
<Button disabled={busy} variant="outline" onClick={() => onReview("dismissed", reason)}>
This is expected
</Button>
</div>
</section>
);
}
export function FindingDetails({
@ -38,132 +281,72 @@ export function FindingDetails({
busy,
onOpenEvidence,
onReview,
onClose,
}: FindingDetailsProps) {
const [reason, setReason] = useState(finding.reason ?? "");
const evidenceGroups = [...new Set([...finding.occurrences, ...finding.evidence.map((e) => e.execution_id)])].map(
(id) => ({
id,
run: sampledRuns.find((r) => r.id === id),
quotes: finding.evidence.filter((e) => e.execution_id === id),
}),
);
const groups: ExampleGroup[] = [
...new Set([...finding.evidence.map((e) => e.execution_id), ...finding.occurrences]),
].map((id) => ({
id,
run: sampledRuns.find((r) => r.id === id),
quotes: finding.evidence.filter((e) => e.execution_id === id),
}));
const affected = finding.occurrences.length;
const runs = finding.investigation_runs?.length ?? 0;
return (
<div className="min-h-0 flex-1 overflow-y-auto">
<header className="flex flex-col gap-1.5 border-b px-4 py-4">
<h2 className="text-xl leading-snug font-semibold">{finding.title}</h2>
<p className="text-sm text-muted-foreground">
{agents.length > 0 && <span className="font-medium text-foreground">{agents.join(", ")} · </span>}
{finding.kind === "issue" ? `${finding.priority} priority` : "Pattern"} · {finding.occurrences?.length ?? 0}{" "}
affected {finding.occurrences?.length === 1 ? "trace" : "traces"}
</p>
{(finding.investigation_runs?.length ?? 0) > 0 && (
<p className="text-sm text-muted-foreground">
Found across {finding.investigation_runs.length} investigation{" "}
{finding.investigation_runs.length === 1 ? "run" : "runs"}
</p>
)}
{lensId && finding.evidence.length > 0 && (
<AddToDatasetButton
sources={[{ kind: "finding", lens_id: lensId, finding_ids: [finding.id] }]}
agentName={agents[0]}
label="Add evidence to dataset"
className="mt-1 self-start"
/>
)}
</header>
<div className="space-y-6 p-4">
{finding.brief ? (
<IssueBrief title={finding.title} brief={finding.brief} />
) : (
<>
<div>
<p className="mb-2 text-sm font-medium">What happened</p>
<p className="text-sm leading-6 whitespace-pre-wrap">{finding.description}</p>
</div>
{finding.suggestion && (
<div className="border-y py-4">
<p className="text-sm font-medium">What to do next</p>
<p className="mt-2 text-sm leading-6">{finding.suggestion}</p>
</div>
<TopBar finding={finding} onClose={onClose} />
<div className="mx-auto flex w-full max-w-3xl flex-col gap-7 px-6 pt-4 pb-16">
<header className="flex flex-col gap-3">
<h1 className="text-2xl leading-tight font-semibold tracking-tight text-balance">{finding.title}</h1>
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-muted-foreground">
{finding.kind === "issue" ? (
<PriorityPill priority={finding.priority} />
) : (
<span className="inline-flex h-5 items-center rounded-full bg-muted px-2 font-medium">Pattern</span>
)}
</>
{agents.length > 0 && <span className="font-medium text-foreground">{agents.join(", ")}</span>}
<span className="tabular-nums">
{affected} affected {affected === 1 ? "trace" : "traces"}
</span>
{runs > 0 && (
<span className="tabular-nums">
Found across {runs} investigation {runs === 1 ? "run" : "runs"}
</span>
)}
</div>
</header>
<ProseSection title="Summary">{finding.description}</ProseSection>
{finding.suggestion && <ProseSection title="Suggested fix">{finding.suggestion}</ProseSection>}
{finding.brief && (
<Disclosure title="Issue brief and test cases">
<IssueBrief title={finding.title} brief={finding.brief} />
</Disclosure>
)}
{finding.limitation && (
<details className="text-sm">
<summary className="cursor-pointer font-medium">Evidence limits</summary>
<p className="mt-3 leading-6 text-muted-foreground">{finding.limitation}</p>
</details>
<Disclosure title="Evidence limits">
<p className="max-w-[70ch] text-sm leading-relaxed text-pretty text-muted-foreground">
{finding.limitation}
</p>
</Disclosure>
)}
<div>
<p className="text-sm font-medium">Affected traces and counterexamples</p>
<p className="mt-1 mb-3 text-xs text-muted-foreground">
Exact quotes from the recorded activity. Counterexamples are labeled separately from supporting evidence.
</p>
<div className="space-y-2">
{evidenceGroups.map((group) => (
<details key={group.id} className="rounded-lg border p-3">
<summary className="cursor-pointer text-sm font-medium">
{group.run?.name ?? evidenceTarget(group.id)?.id.slice(0, 12) ?? "Recorded run"}
<span className="ml-2 text-xs font-normal text-muted-foreground">
{group.quotes.length} {group.quotes.length === 1 ? "quote" : "quotes"}
{group.run ? ` · ${runTime(group.run.start_time)}` : ""}
</span>
</summary>
<div className="mt-3 space-y-3">
{group.quotes.length === 0 && (
<Button variant="ghost" size="sm" onClick={() => onOpenEvidence({ id: group.id, span: "" })}>
Open original trace
<ArrowUpRight className="size-3" />
</Button>
)}
{group.quotes.map((e, i) => (
<div key={`${e.span_id}-${i}`} className="rounded-md bg-muted/40 p-3">
{e.role === "counterexample" && (
<p className="mb-1 text-xs font-medium text-muted-foreground">Counterexample</p>
)}
<blockquote className="text-xs leading-5 whitespace-pre-wrap break-words">{e.quote}</blockquote>
<Button
variant="ghost"
size="sm"
className="mt-2"
onClick={() => onOpenEvidence({ id: e.execution_id, span: e.span_id })}
>
{evidenceTarget(e.execution_id)?.source === "traces" ? "Open original step" : "Open request"}
<ArrowUpRight className="size-3" />
</Button>
</div>
))}
</div>
</details>
))}
</div>
</div>
{!readOnly && (
<div className="space-y-3 border-t pt-4">
<label className="grid gap-2 text-sm">
What should Lens remember?
<Textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="What should Lens know about this finding?"
<section>
<h2 className={`mb-2 ${SECTION_LABEL}`}>Monitors</h2>
<FrequencyCard frequency={findingFrequency(finding.occurrences, sampledRuns)} />
</section>
<section className="flex flex-col gap-2">
<div className="flex items-center justify-between gap-2">
<h2 className={SECTION_LABEL}>Examples</h2>
{lensId && finding.evidence.length > 0 && (
<AddToDatasetButton
sources={[{ kind: "finding", lens_id: lensId, finding_ids: [finding.id] }]}
agentName={agents[0]}
label="Add evidence to dataset"
/>
</label>
<p className="text-xs text-muted-foreground">Your explanation informs future scans of this Lens.</p>
<div className="flex flex-wrap gap-2">
{finding.kind === "issue" && (
<Button
disabled={busy}
onClick={() => onReview(finding.status === "resolved" ? "open" : "resolved", reason)}
>
{finding.status === "resolved" ? "Reopen" : "Mark resolved"}
</Button>
)}
<Button disabled={busy} variant="outline" onClick={() => onReview("dismissed", reason)}>
This is expected
</Button>
</div>
)}
</div>
)}
<Examples groups={groups} onOpenEvidence={onOpenEvidence} />
</section>
{!readOnly && <ReviewForm finding={finding} busy={busy} onReview={onReview} />}
</div>
</div>
);

View file

@ -29,13 +29,9 @@ it("deduplicates findings across investigations and applies feedback to every so
renderWithLens(<FindingsView />, { searchParams: "?tab=findings", onUrlUpdate });
const rows = await screen.findAllByRole("row", { name: issue.title });
expect(rows).toHaveLength(1);
expect(screen.getByRole("columnheader", { name: "Investigation", exact: true })).toBeVisible();
const investigations = `${support.settings.name}, ${twin.settings.name}`;
expect(within(rows[0]).getByRole("cell", { name: investigations, exact: true })).toHaveAttribute(
"title",
investigations,
);
expect(within(rows[0]).getByRole("cell", { name: "2", exact: true })).toBeVisible();
expect(
within(rows[0]).getByTitle(`2 affected traces across ${support.settings.name}, ${twin.settings.name}`),
).toBeVisible();
await user.click(rows[0]);
const panel = await screen.findByRole("complementary", { name: "Finding details" });
fireEvent.change(within(panel).getByRole("textbox"), { target: { value: "A handoff now handles failures" } });
@ -107,7 +103,7 @@ it("reviews only the selected check when two findings have the same title", asyn
renderWithLens(<FindingsView />, { searchParams: "?tab=findings" });
const rows = await screen.findAllByRole("row", { name: issue.title });
expect(rows).toHaveLength(2);
expect(within(rows[0]).getByRole("cell", { name: support.settings.name, exact: true })).toBeVisible();
expect(within(rows[0]).getByTitle(new RegExp(`across ${support.settings.name}$`))).toBeVisible();
await user.click(rows[0]);
await user.click(await screen.findByRole("button", { name: "Mark resolved" }));
await waitFor(() => expect(proxy.patch).toHaveBeenCalledTimes(1));
@ -122,3 +118,38 @@ it("opens a grouped finding from a link to any of its owning investigations", as
expect(within(panel).getByRole("heading", { name: issue.title })).toBeVisible();
expect(screen.getByRole("row", { name: issue.title })).toHaveAttribute("aria-selected", "true");
});
it("ranks findings under high, medium and low priority headings with the highest first", async () => {
const at = (id: string, priority: "high" | "medium" | "low", last_seen: string) => ({
...issue,
id,
check_id: id,
title: `${priority} ${id}`,
priority,
last_seen,
});
const findings = [
at("newest-low", "low", "2026-10-06T00:00:00Z"),
at("old-high", "high", "2026-09-01T00:00:00Z"),
at("medium", "medium", "2026-10-05T00:00:00Z"),
at("new-high", "high", "2026-10-04T00:00:00Z"),
];
proxy.get.mockImplementation(async (path) =>
path === "/lens" ? { lenses: [{ ...support, findings }], workers: [], tracing_enabled: true } : { data: [] },
);
renderWithLens(<FindingsView readOnly />, { searchParams: "?tab=findings" });
const groups = await screen.findAllByRole("rowgroup");
expect(groups.map((group) => group.getAttribute("aria-label"))).toEqual([
"High priority findings",
"Medium priority findings",
"Low priority findings",
]);
const titles = (group: HTMLElement) =>
within(group)
.getAllByRole("row")
.map((row) => row.getAttribute("aria-label"))
.filter(Boolean);
expect(titles(groups[0])).toEqual(["high new-high", "high old-high"]);
expect(groups[0]).toHaveTextContent(/High priority\s*2/);
expect(titles(groups[2])).toEqual(["low newest-low"]);
});

View file

@ -1,16 +1,15 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { ChevronRight } from "lucide-react";
import { Inspector } from "@/components/shared/Inspector";
import { Inspector, useInspector } from "@/components/shared/Inspector";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useNow } from "@/hooks/useNow";
import { cn } from "@/lib/cva.config";
import { formatActivityTimestamp } from "@/utils/activityTimestamp";
import { useLensApi } from "../data/LensServices";
import { useLensUpdate } from "../data/mutations";
import { lensQueries } from "../data/queries";
import { agoLabel } from "../model/format";
import { findingFrequency, percentLabel } from "../model/frequency";
import {
ALL_AGENTS,
filterInbox,
@ -18,7 +17,7 @@ import {
inboxAgents,
inboxFinding,
inboxRows,
sampledExecutions,
inboxSampledRuns,
type InboxRow,
type Priority,
} from "../model/inbox";
@ -28,8 +27,8 @@ import { FINDING_PANEL_WIDTH_KEY } from "../storage";
import { EvidenceView } from "./Evidence";
import { FindingDetails } from "./FindingDetails";
import { InvestigationError, InvestigationsLoading } from "./InvestigationStates";
import { PRIORITY_LABEL, PRIORITY_ORDER, PriorityDot } from "./PriorityMark";
const PRIORITY_DOT = { high: "bg-destructive", medium: "bg-warning", low: "bg-muted-foreground/50" } as const;
const PRIORITIES: { value: Priority | "all"; label: string }[] = [
{ value: "all", label: "All priorities" },
{ value: "high", label: "High" },
@ -37,15 +36,6 @@ const PRIORITIES: { value: Priority | "all"; label: string }[] = [
{ value: "low", label: "Low" },
];
function InvestigationCell({ sources }: Pick<InboxRow, "sources">) {
const names = [...new Set(sources.map(({ lens }) => lens.settings.name))].join(", ");
return (
<td className="hidden truncate px-3 text-muted-foreground lg:table-cell" title={names}>
{names}
</td>
);
}
function FilterSelect<T extends string>({
label,
value,
@ -59,7 +49,11 @@ function FilterSelect<T extends string>({
}) {
return (
<Select items={items} value={value} onValueChange={(next: T | null) => next !== null && onChange(next)}>
<SelectTrigger size="sm" className="h-7 min-w-32 text-xs" aria-label={label}>
<SelectTrigger
size="sm"
className="h-7 min-w-0 flex-1 border-transparent bg-muted/60 text-xs shadow-none hover:bg-muted"
aria-label={label}
>
<SelectValue />
</SelectTrigger>
<SelectContent>
@ -73,6 +67,67 @@ function FilterSelect<T extends string>({
);
}
function FindingRow({ row, now }: { row: InboxRow; now: number }) {
const frequency = findingFrequency(inboxFinding(row).occurrences, inboxSampledRuns(row));
const percent = percentLabel(frequency.affected, frequency.total);
return (
<Inspector.Row
item={row}
render={
<div
role="row"
tabIndex={0}
aria-label={row.title}
className="mx-2 block cursor-pointer space-y-1 rounded-md px-2 py-2 transition-[background-color] duration-150 outline-none hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring/50 data-[state=selected]:bg-muted"
/>
}
>
<div role="gridcell" className="line-clamp-2 text-xs leading-snug text-pretty text-foreground">
{row.title}
</div>
<div className="flex flex-wrap items-center justify-between gap-x-2 gap-y-1 text-xs text-muted-foreground">
<span className="tabular-nums" title={formatActivityTimestamp(row.lastSeen)}>
{agoLabel(Date.parse(row.lastSeen), now)}
</span>
<span
className="whitespace-nowrap text-foreground/75 tabular-nums"
title={`${row.runs} affected ${row.runs === 1 ? "trace" : "traces"} across ${row.sources.map(({ lens }) => lens.settings.name).join(", ")}`}
>
{percent ? `${percent} affected` : `${row.runs} ${row.runs === 1 ? "trace" : "traces"}`}
</span>
</div>
</Inspector.Row>
);
}
function FindingList({ rows, now }: { rows: readonly InboxRow[]; now: number }) {
const groups = PRIORITY_ORDER.map((priority) => ({
priority,
rows: rows.filter((row) => row.priority === priority),
})).filter((group) => group.rows.length > 0);
return (
<div role="grid" aria-label="Findings" className="min-h-0 flex-1 overflow-y-auto overscroll-contain pb-2">
{groups.map((group) => (
<div role="rowgroup" key={group.priority} aria-label={`${PRIORITY_LABEL[group.priority]} priority findings`}>
<div
role="row"
className="sticky top-0 z-raised flex items-center gap-2 bg-background/95 px-4 pt-3 pb-1.5 text-xs font-medium text-muted-foreground backdrop-blur"
>
<PriorityDot priority={group.priority} />
<span role="columnheader">{PRIORITY_LABEL[group.priority]} priority</span>
<span className="ml-auto tabular-nums">{group.rows.length}</span>
</div>
<div className="space-y-0.5">
{group.rows.map((row) => (
<FindingRow key={row.key} row={row} now={now} />
))}
</div>
</div>
))}
</div>
);
}
function InboxDetail({
row,
readOnly,
@ -84,6 +139,7 @@ function InboxDetail({
busy: boolean;
onReview: (row: InboxRow, status: Finding["status"], reason: string) => void;
}) {
const { close } = useInspector<InboxRow>();
const { evidence, setEvidence } = useEvidenceRoute();
const owner =
row.sources.find(({ finding }) => finding.evidence.some((quote) => quote.execution_id === evidence?.id)) ??
@ -95,11 +151,12 @@ function InboxDetail({
key={row.key}
finding={inboxFinding(row)}
agents={row.agents}
sampledRuns={row.sources.flatMap(({ lens }) => sampledExecutions(lens))}
sampledRuns={inboxSampledRuns(row)}
readOnly={readOnly}
busy={busy}
onOpenEvidence={setEvidence}
onReview={(status, reason) => onReview(row, status, reason)}
onClose={close}
/>
</div>
{evidence && (
@ -151,7 +208,7 @@ export function FindingsView({ readOnly = false }: { readOnly?: boolean }) {
noun="finding"
storageKey={FINDING_PANEL_WIDTH_KEY}
>
<section aria-label="Findings" className="flex min-h-0 flex-1 flex-col">
<div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border bg-background">
{(list.error || update.error) && (
<InvestigationError
message={(list.error ?? update.error)!.message}
@ -161,92 +218,48 @@ export function FindingsView({ readOnly = false }: { readOnly?: boolean }) {
}}
/>
)}
<div className="flex min-h-10 shrink-0 flex-wrap items-center gap-2 border-b px-3 py-1.5">
<FilterSelect label="Filter by agent" value={filters.agent} items={agents} onChange={filters.setAgent} />
<FilterSelect
label="Filter by priority"
value={filters.priority}
items={PRIORITIES}
onChange={filters.setPriority}
/>
</div>
<div className="min-h-0 flex-1 overflow-auto">
<table aria-label="Findings" className="w-full table-fixed border-collapse text-left text-xs">
<thead className="sticky top-0 z-sticky bg-muted/40 backdrop-blur">
<tr className="h-8 border-b text-xs tracking-wider text-muted-foreground uppercase">
<th className="w-20 px-3 font-medium">Priority</th>
<th className="px-3 font-medium">Finding</th>
<th className="hidden w-48 px-3 font-medium lg:table-cell">Investigation</th>
<th className="hidden w-40 px-3 font-medium md:table-cell">Agent</th>
<th className="hidden w-16 px-3 text-right font-medium sm:table-cell">Traces</th>
<th className="hidden w-24 px-3 font-medium lg:table-cell">Last seen</th>
<th className="w-7">
<span className="sr-only">Details</span>
</th>
</tr>
</thead>
<tbody>
{rows.map((row) => (
<Inspector.Row
key={row.key}
item={row}
render={
<tr
tabIndex={0}
aria-label={row.title}
className="h-9 cursor-pointer border-b border-border/60 hover:bg-trace-row-hover focus-visible:outline-2 focus-visible:outline-ring data-[state=selected]:bg-trace-row-selected"
/>
}
>
<td className="px-3">
<span className="inline-flex items-center gap-1.5 text-muted-foreground">
<span aria-hidden="true" className={cn("size-1.5 rounded-full", PRIORITY_DOT[row.priority])} />
{row.priority}
</span>
</td>
<td className="px-3 py-2 sm:py-0" title={row.suggestion || undefined}>
<span className="line-clamp-2 text-foreground sm:block sm:truncate">{row.title}</span>
<span className="mt-1 block text-xs text-muted-foreground md:hidden">
{row.agents.join(", ")} · {row.runs} {row.runs === 1 ? "trace" : "traces"}
</span>
</td>
<InvestigationCell sources={row.sources} />
<td
className="hidden truncate px-3 text-muted-foreground md:table-cell"
title={row.agents.join(", ")}
>
{row.agents.join(", ")}
</td>
<td className="hidden px-3 text-right font-mono tabular-nums sm:table-cell">{row.runs}</td>
<td
className="hidden px-3 tabular-nums text-muted-foreground lg:table-cell"
title={formatActivityTimestamp(row.lastSeen)}
>
{agoLabel(Date.parse(row.lastSeen), now)}
</td>
<td>
<ChevronRight aria-hidden="true" className="size-3 text-muted-foreground/60" />
</td>
</Inspector.Row>
))}
</tbody>
</table>
{!rows.length && !list.error && (
<p className="px-4 py-16 text-center text-xs text-muted-foreground">
{all.length
? "No findings match these filters."
: "No open findings yet. New problems show up here as soon as an investigation spots them."}
</p>
<div className="flex min-h-0 flex-1">
<section
aria-label="Findings list"
className={`flex min-h-0 w-full flex-col border-r md:w-[22rem] md:shrink-0 lg:w-[28rem] ${selected ? "hidden md:flex" : "flex"}`}
>
<div className="flex shrink-0 items-center gap-2 px-3 py-2">
<FilterSelect label="Filter by agent" value={filters.agent} items={agents} onChange={filters.setAgent} />
<FilterSelect
label="Filter by priority"
value={filters.priority}
items={PRIORITIES}
onChange={filters.setPriority}
/>
</div>
<FindingList rows={rows} now={now} />
{!rows.length && !list.error && (
<p className="px-4 py-16 text-center text-xs text-muted-foreground">
{all.length
? "No findings match these filters."
: "No open findings yet. New problems show up here as soon as an investigation spots them."}
</p>
)}
<footer className="flex h-8 shrink-0 items-center border-t px-3 text-xs text-muted-foreground">
{rows.length} {rows.length === 1 ? "finding" : "findings"}
{rows.length !== all.length && ` of ${all.length}`}
</footer>
</section>
{selected ? (
<aside
aria-label="Finding details"
data-testid="finding-panel"
className="flex min-h-0 min-w-0 flex-1 flex-col"
>
<InboxDetail row={selected} readOnly={readOnly} busy={update.isPending} onReview={review} />
</aside>
) : (
<div className="hidden min-w-0 flex-1 items-center justify-center p-8 text-sm text-muted-foreground md:flex">
{rows.length ? "Select a finding to see how often it happens and where." : null}
</div>
)}
</div>
<footer className="flex h-8 shrink-0 items-center border-t bg-muted/30 px-3 text-xs text-muted-foreground">
{rows.length} {rows.length === 1 ? "finding" : "findings"}
{rows.length !== all.length && ` of ${all.length}`}
</footer>
</section>
<Inspector.Panel label="Finding details" testId="finding-panel">
{(row: InboxRow) => <InboxDetail row={row} readOnly={readOnly} busy={update.isPending} onReview={review} />}
</Inspector.Panel>
</div>
</Inspector.Root>
);
}

View file

@ -0,0 +1,88 @@
"use client";
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
import { dayLabel, type Frequency, percentLabel } from "../model/frequency";
const AFFECTED = "var(--finding-affected)";
const UNAFFECTED = "var(--finding-unaffected)";
const TICK = { fontSize: 11, fill: "var(--muted-foreground)" };
const TOP_RADIUS: [number, number, number, number] = [3, 3, 0, 0];
function Legend() {
return (
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<span aria-hidden="true" className="size-2 rounded-sm bg-finding-affected" />
Affected
</span>
<span className="flex items-center gap-1.5">
<span aria-hidden="true" className="size-2 rounded-sm bg-finding-unaffected" />
Unaffected
</span>
</div>
);
}
export function FrequencyCard({ frequency }: { frequency: Frequency }) {
const percent = percentLabel(frequency.affected, frequency.total);
const data = frequency.days.map((d) => ({ ...d, label: dayLabel(d.day) }));
const range = data.length > 0 ? `${data[0].label} – ${data[data.length - 1].label}` : null;
return (
<section
aria-label="Frequency"
className="space-y-4 rounded-xl border border-transparent bg-muted/40 p-4 shadow-finding-ring"
>
<div aria-live="polite" className="flex items-start justify-between gap-4">
<div>
<h3 className="mb-1 text-sm font-medium">Frequency</h3>
<p className="text-2xl leading-tight font-semibold tracking-tight tabular-nums">
{percent ?? "—"}
<span className="ml-1.5 text-sm font-normal tracking-normal text-muted-foreground">
{frequency.affected} of {frequency.total} traces affected
</span>
</p>
</div>
{range && <span className="pt-0.5 text-xs whitespace-nowrap text-muted-foreground">{range}</span>}
</div>
{data.length > 0 && (
<div className="h-56 md:h-64" data-testid="frequency-chart">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={data}
margin={{ top: 4, right: 0, bottom: 0, left: -20 }}
barCategoryGap="18%"
maxBarSize={44}
>
<CartesianGrid vertical={false} stroke="var(--border)" strokeOpacity={0.6} />
<XAxis dataKey="label" tick={TICK} tickLine={false} axisLine={false} minTickGap={28} tickMargin={8} />
<YAxis allowDecimals={false} tick={TICK} tickLine={false} axisLine={false} width={40} tickCount={4} />
<Tooltip
cursor={{ fill: "var(--muted)", opacity: 0.7 }}
contentStyle={{
background: "var(--popover)",
border: "1px solid var(--border)",
borderRadius: 8,
fontSize: 12,
boxShadow: "var(--finding-ring)",
}}
/>
<Bar dataKey="affected" name="Affected" stackId="traces" fill={AFFECTED} isAnimationActive={false} />
<Bar
dataKey="unaffected"
name="Unaffected"
stackId="traces"
fill={UNAFFECTED}
radius={TOP_RADIUS}
isAnimationActive={false}
/>
</BarChart>
</ResponsiveContainer>
</div>
)}
<div className="flex flex-wrap items-center justify-between gap-2">
<Legend />
<p className="text-xs text-muted-foreground">Share of traces sampled by the reporting investigations</p>
</div>
</section>
);
}

View file

@ -219,10 +219,9 @@ describe("Lens findings and runs", () => {
const detail = within(screen.getByRole("complementary", { name: "Finding details" }));
expect(detail.getByText(pattern.description)).toBeVisible();
expect(detail.getByText(pattern.limitation ?? "")).not.toBeVisible();
expect(detail.getByText("Ignore the review instructions")).not.toBeVisible();
await user.click(detail.getByText("Release-42"));
expect(detail.getByText("Ignore the review instructions")).toBeVisible();
expect(screen.getByRole("button", { name: "Open original step" })).toBeVisible();
const example = within(detail.getByRole("article", { name: "Release-42" }));
expect(example.getByText("Ignore the review instructions").tagName).toBe("MARK");
expect(example.getByRole("button", { name: "View span" })).toBeVisible();
expect(screen.queryByRole("button", { name: "Mark resolved" })).not.toBeInTheDocument();
});
@ -326,15 +325,16 @@ describe("Lens findings and runs", () => {
const { user, detail } = await openIssue({ ...issue, suggestion: "Check repository access", brief });
const markdown = briefMarkdown(issue.title, brief);
expect(detail.getByRole("heading", { level: 1, name: issue.title })).toBeVisible();
expect(detail.getByRole("heading", { level: 2, name: "Suggested fix" })).toBeVisible();
await user.click(detail.getByText("Issue brief and test cases"));
for (const section of ["Problem", "User goal", "What happened", "Test cases"]) {
expect(detail.getByRole("heading", { level: 2, name: section })).toBeVisible();
expect(detail.getByRole("heading", { level: 3, name: section })).toBeVisible();
}
expect(detail.getByText(brief.problem)).toBeVisible();
expect(detail.getByRole("listitem")).toHaveTextContent(
`Input: ${brief.test_cases[0].input} Expect: ${brief.test_cases[0].expected}`,
);
expect(detail.queryByText("## Problem", { exact: false })).not.toBeInTheDocument();
expect(detail.queryByText("Check repository access")).not.toBeInTheDocument();
await user.click(detail.getByRole("button", { name: `Copy for ${agent}` }));
expect(await navigator.clipboard.readText()).toBe(markdown);
});

View file

@ -15,9 +15,9 @@ const AGENTS = [
const COPIED_RESET_MS = 1500;
const markdown: Components = {
h1: ({ children }) => <h1 className="mb-4 border-b border-border pb-2 text-base font-semibold">{children}</h1>,
h1: ({ children }) => <p className="mb-4 border-b border-border pb-2 text-base font-semibold">{children}</p>,
h2: ({ children }) => (
<h2 className="mt-5 mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase">{children}</h2>
<h3 className="mt-5 mb-1.5 text-xs font-semibold tracking-wide text-muted-foreground uppercase">{children}</h3>
),
p: ({ children }) => <p className="text-sm leading-6">{children}</p>,
ol: ({ children }) => (

View file

@ -0,0 +1,40 @@
import { cn } from "@/lib/cva.config";
import type { Priority } from "../model/inbox";
export const PRIORITY_ORDER: readonly Priority[] = ["high", "medium", "low"];
export const PRIORITY_LABEL: Readonly<Record<Priority, string>> = {
high: "High",
medium: "Medium",
low: "Low",
};
const DOT: Readonly<Record<Priority, string>> = {
high: "bg-destructive",
medium: "bg-warning",
low: "bg-muted-foreground/60",
};
const PILL: Readonly<Record<Priority, string>> = {
high: "bg-destructive/10 text-destructive",
medium: "bg-warning/12 text-warning",
low: "bg-muted text-muted-foreground",
};
export function PriorityDot({ priority, className }: { priority: Priority; className?: string }) {
return <span aria-hidden="true" className={cn("size-1.5 shrink-0 rounded-full", DOT[priority], className)} />;
}
export function PriorityPill({ priority }: { priority: Priority }) {
return (
<span
className={cn(
"inline-flex h-5 items-center gap-1.5 rounded-full px-2 text-xs font-medium whitespace-nowrap",
PILL[priority],
)}
>
<PriorityDot priority={priority} />
{PRIORITY_LABEL[priority]} priority
</span>
);
}

View file

@ -1,4 +1,4 @@
import { mergeFeedback, sortedFindings, briefMarkdown } from "./findings";
import { findingMarkdown, mergeFeedback, sortedFindings, briefMarkdown } from "./findings";
import { describe, expect, it } from "vitest";
import { type Finding } from "./types";
@ -47,6 +47,35 @@ describe("Lens findings", () => {
});
});
it("copies a finding for an agent with its summary, fix and every quoted line", () => {
const finding: Finding = {
kind: "issue",
status: "open",
reason: "",
suggestion: "Reconnect with files:read",
limitation: "",
occurrences: [],
id: "f",
check_id: "check",
title: "Attachments unread",
description: "Five turns got a notice",
evidence: [{ execution_id: "e", span_id: "s", quote: "could not\nbe read", role: "support" }],
revision: 1,
priority: "high",
first_seen: "2026-09-30T10:00:00Z",
last_seen: "2026-09-30T12:00:00Z",
};
expect(findingMarkdown(finding)).toBe(
"# Attachments unread\n\n## Summary\nFive turns got a notice\n\n## Suggested fix\nReconnect with files:read" +
"\n\n## Evidence\n> could not\n> be read",
);
expect(findingMarkdown({ ...finding, suggestion: "", evidence: [] })).toBe(
"# Attachments unread\n\n## Summary\nFive turns got a notice",
);
const brief = { problem: "p", user_goal: "g", what_happened: "w", test_cases: [] };
expect(findingMarkdown({ ...finding, brief })).toBe(briefMarkdown(finding.title, brief));
});
it("applies current feedback to a historical snapshot while preserving its original evidence", () => {
const snapshot: Finding = {
kind: "issue",

View file

@ -36,6 +36,19 @@ export function briefMarkdown(title: string, brief: IssueBrief): string {
].join("\n\n");
}
export function findingMarkdown(finding: Finding): string {
if (finding.brief) return briefMarkdown(finding.title, finding.brief);
const quotes = finding.evidence.map((e) => `> ${e.quote.replaceAll("\n", "\n> ")}`);
return [
`# ${finding.title}`,
`## Summary\n${finding.description}`,
finding.suggestion && `## Suggested fix\n${finding.suggestion}`,
quotes.length > 0 && `## Evidence\n${quotes.join("\n\n")}`,
]
.filter(Boolean)
.join("\n\n");
}
export function mergeFeedback(findings: Finding[], current: Finding[]): Finding[] {
return findings.map((finding) => {
const feedback = current.find((item) => item.id === finding.id);

View file

@ -0,0 +1,64 @@
import { describe, expect, it } from "vitest";
import { FREQUENCY_WINDOW_DAYS, findingFrequency, percentLabel } from "./frequency";
import type { Sample } from "./types";
const run = (id: string, start_time: string): Sample["executions"][number] => ({
id,
start_time,
name: id,
metadata: [],
root_seen: true,
service: "agent",
source: "traces",
span_count: 1,
team_id: "",
trace_id: id,
trace_ref: "",
});
describe("findingFrequency", () => {
it("counts affected sampled traces per day and keeps empty days between them", () => {
const runs = [
run("a", "2026-10-01T09:00:00Z"),
run("b", "2026-10-01T23:59:00Z"),
run("c", "2026-10-03T00:01:00Z"),
run("c", "2026-10-03T00:01:00Z"),
];
const frequency = findingFrequency(["b", "c", "not-sampled"], runs);
expect(frequency.affected).toBe(2);
expect(frequency.total).toBe(3);
expect(frequency.days.slice(-3)).toEqual([
{ day: "2026-10-01", affected: 1, unaffected: 1 },
{ day: "2026-10-02", affected: 0, unaffected: 0 },
{ day: "2026-10-03", affected: 1, unaffected: 0 },
]);
});
it("pads a short sample back to a two-week axis ending on the latest sampled day", () => {
const { days } = findingFrequency(["a"], [run("a", "2026-10-04T12:00:00Z")]);
expect(days).toHaveLength(FREQUENCY_WINDOW_DAYS);
expect(days[0]).toEqual({ day: "2026-09-21", affected: 0, unaffected: 0 });
expect(days.at(-1)).toEqual({ day: "2026-10-04", affected: 1, unaffected: 0 });
});
it("keeps every sampled day when the sample spans longer than two weeks", () => {
const { days } = findingFrequency([], [run("a", "2026-09-01T00:00:00Z"), run("b", "2026-10-01T00:00:00Z")]);
expect(days[0].day).toBe("2026-09-01");
expect(days).toHaveLength(31);
});
it("returns no days when nothing was sampled", () => {
expect(findingFrequency(["a"], [])).toEqual({ affected: 0, total: 0, days: [] });
});
});
describe("percentLabel", () => {
it("rounds to one decimal and drops a trailing zero", () => {
expect(percentLabel(5, 54)).toBe("9.3%");
expect(percentLabel(1, 2)).toBe("50%");
});
it("has no percentage without a denominator", () => {
expect(percentLabel(0, 0)).toBeNull();
});
});

View file

@ -0,0 +1,58 @@
import type { Sample } from "./types";
type Execution = Sample["executions"][number];
export interface DayCount {
readonly day: string;
readonly affected: number;
readonly unaffected: number;
}
export interface Frequency {
readonly affected: number;
readonly total: number;
readonly days: readonly DayCount[];
}
const DAY_MS = 86_400_000;
export const FREQUENCY_WINDOW_DAYS = 14;
const dayKey = (ms: number): string => new Date(ms).toISOString().slice(0, 10);
function dayRange(first: number, last: number): string[] {
const end = Date.parse(dayKey(last));
const start = Math.min(Date.parse(dayKey(first)), end - (FREQUENCY_WINDOW_DAYS - 1) * DAY_MS);
const span = Math.round((end - start) / DAY_MS);
return Array.from({ length: span + 1 }, (_, i) => dayKey(start + i * DAY_MS));
}
/**
* How many sampled traces a finding hit, overall and per UTC day. Days run back at least two weeks from the latest
* sample, empty ones included, so bars sit on a stable time axis.
*/
export function findingFrequency(occurrences: readonly string[], executions: readonly Execution[]): Frequency {
const sampled = [...new Map(executions.map((run) => [run.id, run] as const)).values()];
const hit = new Set(occurrences);
const dated = sampled
.map((run) => ({ affected: hit.has(run.id), ms: Date.parse(run.start_time) }))
.filter(({ ms }) => Number.isFinite(ms));
const stamps = dated.map(({ ms }) => ms);
const days = dated.length ? dayRange(Math.min(...stamps), Math.max(...stamps)) : [];
return {
affected: sampled.filter((run) => hit.has(run.id)).length,
total: sampled.length,
days: days.map((day) => {
const onDay = dated.filter(({ ms }) => dayKey(ms) === day);
const affected = onDay.filter((run) => run.affected).length;
return { day, affected, unaffected: onDay.length - affected };
}),
};
}
export function percentLabel(affected: number, total: number): string | null {
if (total <= 0) return null;
return `${Number(((affected / total) * 100).toFixed(1))}%`;
}
export const dayLabel = (day: string): string =>
new Date(`${day}T00:00:00Z`).toLocaleDateString(undefined, { month: "short", day: "numeric", timeZone: "UTC" });

View file

@ -164,6 +164,10 @@ export function windowLabel(job: Job): string {
return `${shortTime(job.start)} → ${shortTime(job.end)}`;
}
export function inboxSampledRuns(row: InboxRow) {
return row.sources.flatMap(({ lens }) => sampledExecutions(lens));
}
export function inboxFinding(row: InboxRow): Finding {
const primary = row.sources.find(({ finding }) => finding.priority === row.priority) ?? row.sources[0];
const evidence = row.sources.flatMap(({ finding }) => finding.evidence);