Skip to content

Commit 1db082c

Browse files
ralyodioclaude
andcommitted
feat: top recommendations per engine on Premium tab
Premium tab was showing engine cards with just pass/warn/fail counts — the cards looked thin and weren't actionable. Now each card lists up to 4 top-priority fail/warn findings (P1-P3) inline, so you can see what each engine flagged without leaving the page. One round-trip via `.in('audit_id', siblingIds)` instead of N queries. PostgREST doesn't support per-group LIMIT so we fetch all qualifying findings ordered by priority and cap to 4-per-engine in code. The same per-engine markdown is already stitched into the consolidated PDF that the summary email attaches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 483ada4 commit 1db082c

2 files changed

Lines changed: 84 additions & 1 deletion

File tree

‎app/(app)/audits/[id]/page.tsx‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,46 @@ export default async function AuditPage({
5454
.eq("owner_id", user!.id)
5555
.order("created_at", { ascending: true })
5656
: { data: null };
57-
const premiumSiblings = (siblingsData ?? []) as unknown as PremiumSibling[];
57+
const premiumSiblingsBase = (siblingsData ?? []) as unknown as Omit<
58+
PremiumSibling,
59+
"topFindings"
60+
>[];
61+
62+
// Pull a handful of top-priority findings per sibling for inline display.
63+
// One round-trip via IN; we filter to fail/warn priorities ≤3 and slice
64+
// client-side because PostgREST doesn't do per-group LIMIT.
65+
let premiumSiblings: PremiumSibling[] = [];
66+
if (premiumSiblingsBase.length > 0) {
67+
const siblingIds = premiumSiblingsBase.map((s) => s.id);
68+
const { data: findingsForSiblings } = await supabase
69+
.from("audit_findings")
70+
.select("audit_id, section, status, title, detail, priority")
71+
.in("audit_id", siblingIds)
72+
.in("status", ["fail", "warn"])
73+
.lte("priority", 3)
74+
.order("priority", { ascending: true });
75+
const byAudit = new Map<
76+
string,
77+
{ section: string; status: string; title: string; detail: string | null; priority: number }[]
78+
>();
79+
for (const f of findingsForSiblings ?? []) {
80+
const arr = byAudit.get(f.audit_id as string) ?? [];
81+
if (arr.length < 4) {
82+
arr.push({
83+
section: f.section as string,
84+
status: f.status as string,
85+
title: f.title as string,
86+
detail: (f.detail as string | null) ?? null,
87+
priority: f.priority as number,
88+
});
89+
}
90+
byAudit.set(f.audit_id as string, arr);
91+
}
92+
premiumSiblings = premiumSiblingsBase.map((s) => ({
93+
...s,
94+
topFindings: byAudit.get(s.id) ?? [],
95+
})) as PremiumSibling[];
96+
}
5897

5998
const { data: findingsData } = await supabase
6099
.from("audit_findings")

‎components/report/premium-engines.tsx‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import Link from "next/link";
22
import { ENGINES, type Engine } from "@/lib/credits";
33
import { ScoreBadge } from "@/components/score-badge";
44

5+
export type PremiumFinding = {
6+
section: string;
7+
status: string;
8+
title: string;
9+
detail: string | null;
10+
priority: number;
11+
};
12+
513
export type PremiumSibling = {
614
id: string;
715
engine: Engine;
@@ -11,6 +19,7 @@ export type PremiumSibling = {
1119
failed_reason: string | null;
1220
completed_at: string | null;
1321
summary: { pass?: number; warn?: number; fail?: number } | null;
22+
topFindings?: PremiumFinding[];
1423
};
1524

1625
export function PremiumEngines({ siblings }: { siblings: PremiumSibling[] }) {
@@ -116,6 +125,41 @@ function EngineCard({ audit }: { audit: PremiumSibling }) {
116125
</p>
117126
)}
118127

128+
{audit.status === "complete" &&
129+
audit.topFindings &&
130+
audit.topFindings.length > 0 && (
131+
<div className="mt-3 border-t border-[var(--color-border)] pt-3">
132+
<div className="text-xs font-semibold uppercase tracking-wider text-[var(--color-muted)]">
133+
Top recommendations
134+
</div>
135+
<ul className="mt-2 space-y-1.5 text-xs">
136+
{audit.topFindings.map((f, i) => (
137+
<li key={i} className="flex items-start gap-2">
138+
<span
139+
aria-hidden
140+
className={
141+
f.status === "fail"
142+
? "mt-0.5 inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--color-fail)]"
143+
: "mt-0.5 inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--color-warn)]"
144+
}
145+
/>
146+
<span className="min-w-0">
147+
<span className="font-medium text-[var(--color-fg)]">
148+
P{f.priority} · {f.title}
149+
</span>
150+
{f.detail && (
151+
<span className="text-[var(--color-muted)]">
152+
{" "}
153+
— {f.detail}
154+
</span>
155+
)}
156+
</span>
157+
</li>
158+
))}
159+
</ul>
160+
</div>
161+
)}
162+
119163
<div className="mt-3 flex items-center gap-3 text-xs">
120164
{audit.status === "complete" && (
121165
<Link

0 commit comments

Comments
 (0)