Commit67dc4e1unknown_key
Show AI Trio Review as three verdict pills on PR page
Show AI Trio Review as three verdict pills on PR page Adds three inline verdict pills — Security ✓/✗/⟳, Correctness ✓/✗/⟳, Style ✓/✗/⟳ — to the PR detail header's meta row. Each pill links to #trio-review-section (the existing TrioReviewGrid card grid) so clicking scrolls directly to the full findings. Pills are only rendered when AI_TRIO_REVIEW_ENABLED=1 and at least one trio persona comment exists. CSS: .trio-pill / .trio-pills-wrap classes added to PRS_DETAIL_STYLES, matching the dark theme palette (green/red/gray). https://claude.ai/code/session_01DzJMTFASjMHt2f5ze4cNLR
1 file changed+104−167dc4e18f15cd0d3a8fe7c8d072a4c9002e232bf
1 changed file+104−1
Modifiedsrc/routes/pulls.tsx+104−1View fileUnifiedSplit
@@ -56,6 +56,7 @@ import { isAiReviewEnabled, triggerAiReview } from "../lib/ai-review";
5656import {
5757 TRIO_COMMENT_MARKER,
5858 TRIO_SUMMARY_MARKER,
59 isTrioReviewEnabled,
5960 type TrioPersona,
6061} from "../lib/ai-review-trio";
6162import {
@@ -1423,6 +1424,40 @@ const PRS_DETAIL_STYLES = `
14231424 .prs-ci-pill.is-failure, .prs-ci-pill.is-error { color: #f87171; background: rgba(248,113,113,0.10); }
14241425 .prs-ci-link { font-size: 12px; color: var(--accent); text-decoration: none; }
14251426 .prs-ci-link:hover { text-decoration: underline; }
1427
1428 /* ─── AI Trio verdict pills (header summary) ─── */
1429 .trio-pill {
1430 display: inline-flex; align-items: center; gap: 4px;
1431 padding: 2px 8px;
1432 font-size: 11px;
1433 font-weight: 700;
1434 border-radius: 9999px;
1435 border: 1px solid transparent;
1436 text-decoration: none;
1437 line-height: 1.6;
1438 letter-spacing: 0.01em;
1439 cursor: pointer;
1440 transition: opacity 120ms ease;
1441 }
1442 .trio-pill:hover { opacity: 0.8; }
1443 .trio-pill.is-pass {
1444 color: #34d399;
1445 background: rgba(52,211,153,0.10);
1446 border-color: rgba(52,211,153,0.35);
1447 }
1448 .trio-pill.is-fail {
1449 color: #f87171;
1450 background: rgba(248,113,113,0.10);
1451 border-color: rgba(248,113,113,0.35);
1452 }
1453 .trio-pill.is-pending {
1454 color: var(--text-muted);
1455 background: rgba(255,255,255,0.04);
1456 border-color: var(--border-strong);
1457 }
1458 .trio-pills-wrap {
1459 display: inline-flex; align-items: center; gap: 4px;
1460 }
14261461`;
14271462
14281463/**
@@ -1821,7 +1856,7 @@ function TrioReviewGrid({ comments }: { comments: TrioCommentLike[] }) {
18211856 const disagreements = summaryBody ? parseDisagreements(summaryBody) : [];
18221857
18231858 return (
1824 <div class="trio-wrap">
1859 <div class="trio-wrap" id="trio-review-section">
18251860 <div class="trio-header">
18261861 <span class="trio-header-dot" aria-hidden="true"></span>
18271862 <strong>AI Trio Review</strong>
@@ -1909,6 +1944,71 @@ function stripTrioHeading(body: string): string {
19091944 .trim();
19101945}
19111946
1947/**
1948 * Three small verdict pills rendered inline in the PR header. Each pill
1949 * links to the `#trio-review-section` anchor so clicking scrolls to the
1950 * full card grid. Only shown when `AI_TRIO_REVIEW_ENABLED=1` and at
1951 * least one persona comment exists.
1952 */
1953function TrioVerdictPills({
1954 comments,
1955}: {
1956 comments: TrioCommentLike[];
1957}) {
1958 if (!isTrioReviewEnabled()) return null;
1959
1960 // Find latest persona verdicts (same logic as TrioReviewGrid).
1961 const latest: Partial<Record<TrioPersona, string>> = {};
1962 for (let i = comments.length - 1; i >= 0; i--) {
1963 const body = comments[i].body || "";
1964 if (!isTrioComment(body)) continue;
1965 if (body.includes(TRIO_SUMMARY_MARKER)) continue;
1966 const persona = trioPersonaOfComment(body);
1967 if (persona && !latest[persona]) latest[persona] = body;
1968 }
1969
1970 const anyPersona = TRIO_PERSONAS.some((p) => !!latest[p]);
1971 if (!anyPersona) return null;
1972
1973 const PERSONA_LABEL: Record<TrioPersona, string> = {
1974 security: "Security",
1975 correctness: "Correctness",
1976 style: "Style",
1977 };
1978 const PERSONA_ICON: Record<TrioPersona, string> = {
1979 security: "🛡",
1980 correctness: "✓",
1981 style: "✎",
1982 };
1983
1984 return (
1985 <span class="trio-pills-wrap" aria-label="AI Trio Review verdicts">
1986 {TRIO_PERSONAS.map((persona) => {
1987 const body = latest[persona];
1988 const verdict = body ? trioVerdictOfBody(body) : null;
1989 const stateClass =
1990 verdict === "pass"
1991 ? "is-pass"
1992 : verdict === "fail"
1993 ? "is-fail"
1994 : "is-pending";
1995 const glyph =
1996 verdict === "pass" ? "✓" : verdict === "fail" ? "✗" : "⟳";
1997 return (
1998 <a
1999 href="#trio-review-section"
2000 class={`trio-pill ${stateClass}`}
2001 title={`AI ${PERSONA_LABEL[persona]} Review — ${verdict === "pass" ? "Pass" : verdict === "fail" ? "Fail" : "Pending"}`}
2002 >
2003 <span aria-hidden="true">{PERSONA_ICON[persona]}</span>
2004 {PERSONA_LABEL[persona]} {glyph}
2005 </a>
2006 );
2007 })}
2008 </span>
2009 );
2010}
2011
19122012// List PRs
19132013pulls.get("/:owner/:repo/pulls", softAuth, requireRepoAccess("read"), async (c) => {
19142014 const { owner: ownerName, repo: repoName } = c.req.param();
@@ -3572,6 +3672,9 @@ pulls.get("/:owner/:repo/pulls/:number", softAuth, requireRepoAccess("read"), as
35723672 {prSizeInfo.label}
35733673 </span>
35743674 )}
3675 <TrioVerdictPills
3676 comments={comments.map(({ comment }) => comment)}
3677 />
35753678 <span>
35763679 <strong>{author?.username}</strong> wants to merge
35773680 </span>
35783681