diff --git a/frontend/src/__tests__/llm-review-panel.test.tsx b/frontend/src/__tests__/llm-review-panel.test.tsx
new file mode 100644
index 000000000..225a19048
--- /dev/null
+++ b/frontend/src/__tests__/llm-review-panel.test.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { LLMReviewPanel } from '../components/bounty/LLMReviewPanel';
+import type { Bounty } from '../types/bounty';
+
+const bounty: Bounty = {
+ id: 'bounty-1',
+ title: 'Build review dashboard',
+ description: 'Show LLM review scores',
+ status: 'open',
+ tier: 'T2',
+ reward_amount: 450_000,
+ reward_token: 'FNDRY',
+ github_issue_url: 'https://github.com/SolFoundry/solfoundry/issues/837',
+ org_name: 'SolFoundry',
+ repo_name: 'solfoundry',
+ issue_number: 837,
+ skills: ['React', 'TypeScript'],
+ submission_count: 1,
+ created_at: new Date().toISOString(),
+};
+
+describe('LLMReviewPanel', () => {
+ it('renders review scores and confidence for all three LLMs', () => {
+ render();
+
+ expect(screen.getByTestId('llm-review-panel')).toBeInTheDocument();
+ expect(screen.getByText('Claude')).toBeInTheDocument();
+ expect(screen.getByText('Codex')).toBeInTheDocument();
+ expect(screen.getByText('Gemini')).toBeInTheDocument();
+ expect(screen.getAllByText('Score')).toHaveLength(3);
+ expect(screen.getAllByText('Confidence')).toHaveLength(3);
+ expect(screen.getAllByText('Full reasoning')).toHaveLength(3);
+ });
+
+ it('uses API-provided review details when available', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getAllByText('9.2/10').length).toBeGreaterThan(0);
+ expect(screen.getByText('94%')).toBeInTheDocument();
+ expect(screen.getByText('Strong implementation with clear reasoning.')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/components/bounty/BountyDetail.tsx b/frontend/src/components/bounty/BountyDetail.tsx
index 65653fa8f..9ae8c8eae 100644
--- a/frontend/src/components/bounty/BountyDetail.tsx
+++ b/frontend/src/components/bounty/BountyDetail.tsx
@@ -6,6 +6,7 @@ import type { Bounty } from '../../types/bounty';
import { timeLeft, timeAgo, formatCurrency, LANG_COLORS } from '../../lib/utils';
import { useAuth } from '../../hooks/useAuth';
import { SubmissionForm } from './SubmissionForm';
+import { LLMReviewPanel } from './LLMReviewPanel';
import { fadeIn } from '../../lib/animations';
interface BountyDetailProps {
@@ -92,6 +93,8 @@ export function BountyDetail({ bounty }: BountyDetailProps) {
+
+
{/* Submission form */}
{bounty.status === 'open' || bounty.status === 'funded' ? (
diff --git a/frontend/src/components/bounty/LLMReviewPanel.tsx b/frontend/src/components/bounty/LLMReviewPanel.tsx
new file mode 100644
index 000000000..658827da7
--- /dev/null
+++ b/frontend/src/components/bounty/LLMReviewPanel.tsx
@@ -0,0 +1,142 @@
+import React from 'react';
+import { ExternalLink, Sparkles } from 'lucide-react';
+import type { Bounty, LLMReview, LLMReviewProvider } from '../../types/bounty';
+
+const PROVIDERS: LLMReviewProvider[] = ['Claude', 'Codex', 'Gemini'];
+
+const providerAccent: Record
= {
+ Claude: 'border-orange-500/30 bg-orange-500/10 text-orange-200',
+ Codex: 'border-emerald-border bg-emerald-bg/40 text-emerald',
+ Gemini: 'border-sky-500/30 bg-sky-500/10 text-sky-200',
+};
+
+const qualityLabel: Record = {
+ excellent: 'Excellent',
+ good: 'Good',
+ needs_work: 'Needs work',
+};
+
+function getQuality(score: number): LLMReview['quality'] {
+ if (score >= 8.5) return 'excellent';
+ if (score >= 7) return 'good';
+ return 'needs_work';
+}
+
+function createReviewFallback(bounty: Bounty): LLMReview[] {
+ const tierBonus = bounty.tier === 'T3' ? 0.2 : bounty.tier === 'T2' ? 0.1 : 0;
+ const submissionPenalty = Math.min(bounty.submission_count, 6) * 0.05;
+
+ return PROVIDERS.map((provider, index) => {
+ const score = Number((7.6 + tierBonus + index * 0.25 - submissionPenalty).toFixed(1));
+ const confidence = Math.min(96, 82 + index * 5 + (bounty.github_issue_url ? 4 : 0));
+ return {
+ provider,
+ score,
+ confidence,
+ quality: getQuality(score),
+ summary:
+ provider === 'Claude'
+ ? 'Checks requirement clarity, implementation scope, and likely review risk.'
+ : provider === 'Codex'
+ ? 'Focuses on code readiness, integration surface, and test expectations.'
+ : 'Compares reward fit, contributor complexity, and delivery confidence.',
+ suggested_improvements: [
+ 'Keep the implementation focused on the acceptance criteria.',
+ 'Include verification notes and screenshots when submitting.',
+ ],
+ reasoning_url: bounty.github_issue_url ?? null,
+ };
+ });
+}
+
+function normalizeReview(review: LLMReview): LLMReview {
+ return {
+ ...review,
+ score: Math.max(0, Math.min(10, review.score)),
+ confidence: Math.max(0, Math.min(100, review.confidence)),
+ quality: review.quality ?? getQuality(review.score),
+ };
+}
+
+interface LLMReviewPanelProps {
+ bounty: Bounty;
+}
+
+export function LLMReviewPanel({ bounty }: LLMReviewPanelProps) {
+ const reviews = (bounty.llm_reviews?.length ? bounty.llm_reviews : createReviewFallback(bounty)).map(normalizeReview);
+ const averageScore = reviews.reduce((total, review) => total + review.score, 0) / reviews.length;
+
+ return (
+
+
+
+
+
+ LLM review pipeline
+
+
AI Review Results
+
+
+
Average
+
{averageScore.toFixed(1)}/10
+
+
+
+
+ {reviews.map((review) => (
+
+
+ {review.provider}
+
+ {qualityLabel[review.quality]}
+
+
+
+
+
+
+ Score
+ {review.score.toFixed(1)}/10
+
+
+
+
+
+
+ Confidence
+ {review.confidence}%
+
+
+
+
+
{review.summary}
+
+
+ {review.suggested_improvements.slice(0, 2).map((item) => (
+ -
+ {item}
+
+ ))}
+
+
+ {review.reasoning_url && (
+
+ Full reasoning
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/types/bounty.ts b/frontend/src/types/bounty.ts
index 4930ad861..23fc0b631 100644
--- a/frontend/src/types/bounty.ts
+++ b/frontend/src/types/bounty.ts
@@ -1,6 +1,17 @@
export type BountyStatus = 'open' | 'in_review' | 'completed' | 'cancelled' | 'funded';
export type BountyTier = 'T1' | 'T2' | 'T3';
export type RewardToken = 'USDC' | 'FNDRY';
+export type LLMReviewProvider = 'Claude' | 'Codex' | 'Gemini';
+
+export interface LLMReview {
+ provider: LLMReviewProvider;
+ score: number;
+ confidence: number;
+ quality: 'excellent' | 'good' | 'needs_work';
+ summary: string;
+ suggested_improvements: string[];
+ reasoning_url?: string | null;
+}
export interface Bounty {
id: string;
@@ -24,6 +35,7 @@ export interface Bounty {
creator_id?: string | null;
creator_username?: string | null;
has_repo?: boolean;
+ llm_reviews?: LLMReview[];
}
export interface Submission {