diff --git a/components/mobile/AnalyzerFeedRow.tsx b/components/mobile/AnalyzerFeedRow.tsx
new file mode 100644
index 0000000..a73ecbd
--- /dev/null
+++ b/components/mobile/AnalyzerFeedRow.tsx
@@ -0,0 +1,79 @@
+'use client';
+
+/* AnalyzerFeedRow — phase 06 (ANL-02).
+ * Purpose: Feed row card — header (ticket# + time-ago), title (1-line truncate),
+ * summary (2-line clamp), footer (stage pips left + confidence/review right).
+ * Entire card is a Link to /mobile/analyzer/[id]. Per D-10..D-12, D-17.
+ * Props: AnalyzerFeedRow type from @/app/api/mobile/analyzer/feed/route. */
+
+import Link from 'next/link';
+import { Card, CardContent } from '@/components/ui/card';
+import { Badge } from '@/components/ui/badge';
+import { AnalyzerStagePips } from '@/components/mobile/AnalyzerStagePips';
+import { ConfidenceBadge } from '@/components/mobile/ConfidenceBadge';
+import type { AnalyzerFeedRow as AnalyzerFeedRowType } from '@/app/api/mobile/analyzer/feed/route';
+
+function relTime(ts: string | null): string {
+ if (!ts) return '—';
+ const diff = Date.now() - new Date(ts).getTime();
+ const m = Math.floor(diff / 60000);
+ if (m < 60) return `${m}m ago`;
+ const h = Math.floor(m / 60);
+ if (h < 24) return `${h}h ago`;
+ return `${Math.floor(h / 24)}d ago`;
+}
+
+export interface AnalyzerFeedRowProps {
+ row: AnalyzerFeedRowType;
+}
+
+export function AnalyzerFeedRow({ row }: AnalyzerFeedRowProps) {
+ return (
+
+
+
+ {/* Line 1 — header row */}
+
+
+ {row.ticketNumber}
+
+
+ {relTime(row.completedAt)}
+
+
+
+ {/* Line 2 — title */}
+
+ {row.title}
+
+
+ {/* Line 3 — summary clamp (2 lines, fallback "—") */}
+
+ {row.summary ?? '—'}
+
+
+ {/* Footer — pips left, badges right */}
+
+
+
+
+ {row.needsHumanReview && (
+
+ Review
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/components/mobile/AnalyzerRowSkeleton.tsx b/components/mobile/AnalyzerRowSkeleton.tsx
new file mode 100644
index 0000000..32896a4
--- /dev/null
+++ b/components/mobile/AnalyzerRowSkeleton.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+/* AnalyzerRowSkeleton — phase 06 (D-28).
+ * Purpose: Skeleton placeholder matching analyzer feed row shape (no priority stripe per D-11).
+ * Renders 5 instances on initial load.
+ * Props: none — purely presentational. */
+
+import { Card, CardContent } from '@/components/ui/card';
+import { Skeleton } from '@/components/ui/skeleton';
+
+export function AnalyzerRowSkeleton() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/mobile/AnalyzerStagePips.tsx b/components/mobile/AnalyzerStagePips.tsx
new file mode 100644
index 0000000..d311676
--- /dev/null
+++ b/components/mobile/AnalyzerStagePips.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+/* AnalyzerStagePips — phase 06 (ANL-02).
+ * Purpose: 3-dot stage indicator (Triage → Analyze → Deep Review) with caret separators.
+ * Filled when stage was used, muted when not. Per D-13/D-14 (no animation).
+ * Props: see AnalyzerStagePipsProps. */
+
+export interface AnalyzerStagePipsProps {
+ haikuUsed: boolean;
+ sonnetUsed: boolean;
+ opusUsed: boolean;
+}
+
+const STAGE_LABELS = ['Triage', 'Analyze', 'Deep Review'];
+
+export function AnalyzerStagePips({ haikuUsed, sonnetUsed, opusUsed }: AnalyzerStagePipsProps) {
+ const used = [haikuUsed, sonnetUsed, opusUsed];
+ const completed = STAGE_LABELS.filter((_, i) => used[i]);
+ const srLabel = completed.length === 0
+ ? 'No stages completed'
+ : `Stages completed: ${completed.join(', ')}`;
+ return (
+
+ {srLabel}
+ {used.map((isUsed, i) => (
+
+
+ {i < 2 && ›}
+
+ ))}
+
+ );
+}
diff --git a/components/mobile/ConfidenceBadge.tsx b/components/mobile/ConfidenceBadge.tsx
new file mode 100644
index 0000000..c44ec92
--- /dev/null
+++ b/components/mobile/ConfidenceBadge.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+/* ConfidenceBadge — phase 06 (ANL-02).
+ * Purpose: Bucketed confidence label — High (>=0.85) / Medium (>=0.65) / Low (<0.65).
+ * Renders nothing when score is null. Per D-15 / D-16.
+ * Props: see ConfidenceBadgeProps. */
+
+import { Badge } from '@/components/ui/badge';
+
+export interface ConfidenceBadgeProps {
+ score: number | null;
+}
+
+export function ConfidenceBadge({ score }: ConfidenceBadgeProps) {
+ if (score === null) return null;
+
+ let label: string;
+ let className: string;
+ if (score >= 0.85) {
+ label = 'High';
+ className = 'bg-green-500/10 text-green-700 dark:text-green-400';
+ } else if (score >= 0.65) {
+ label = 'Medium';
+ className = 'bg-amber-500/10 text-amber-700 dark:text-amber-400';
+ } else {
+ label = 'Low';
+ className = 'bg-slate-500/10 text-slate-600 dark:text-slate-400';
+ }
+
+ return (
+
+ {label}
+
+ );
+}