diff --git a/dashboard/src/components/deliberation/ConflictCard.tsx b/dashboard/src/components/deliberation/ConflictCard.tsx
new file mode 100644
index 0000000..b4f8922
--- /dev/null
+++ b/dashboard/src/components/deliberation/ConflictCard.tsx
@@ -0,0 +1,57 @@
+import type { Conflict, ConflictNature } from '../../types/api';
+
+interface ConflictCardProps {
+ conflict: Conflict;
+}
+
+const natureConfig: Record<
+ ConflictNature,
+ { bg: string; text: string; label: string }
+> = {
+ contradictory: {
+ bg: 'bg-red-100',
+ text: 'text-red-800',
+ label: 'Contradictory',
+ },
+ trade_off: {
+ bg: 'bg-yellow-100',
+ text: 'text-yellow-800',
+ label: 'Trade-off',
+ },
+ overlapping: {
+ bg: 'bg-blue-100',
+ text: 'text-blue-800',
+ label: 'Overlapping',
+ },
+};
+
+export function ConflictCard({ conflict }: ConflictCardProps) {
+ const config = natureConfig[conflict.nature];
+
+ return (
+
+
+
+ {config.label}
+
+
+ Weight: {conflict.severity_weight.toFixed(2)}
+
+
+
{conflict.description}
+ {conflict.resolution && (
+
+
+ Resolution
+
+
{conflict.resolution}
+
+ )}
+
+ Findings involved: {conflict.finding_ids.join(', ')}
+
+
+ );
+}
diff --git a/dashboard/src/components/deliberation/DeliberationTimeline.tsx b/dashboard/src/components/deliberation/DeliberationTimeline.tsx
new file mode 100644
index 0000000..e387169
--- /dev/null
+++ b/dashboard/src/components/deliberation/DeliberationTimeline.tsx
@@ -0,0 +1,119 @@
+import { useState } from 'react';
+import type { DeliberationStep, StepType } from '../../types/api';
+
+interface DeliberationTimelineProps {
+ steps: DeliberationStep[];
+}
+
+const stepConfig: Record<
+ StepType,
+ { icon: string; color: string; bg: string; label: string }
+> = {
+ merge: {
+ icon: '๐',
+ color: 'text-blue-600',
+ bg: 'bg-blue-100',
+ label: 'Merge',
+ },
+ conflict_detection: {
+ icon: 'โ ๏ธ',
+ color: 'text-yellow-600',
+ bg: 'bg-yellow-100',
+ label: 'Conflict Detection',
+ },
+ synthesis: {
+ icon: '๐งช',
+ color: 'text-purple-600',
+ bg: 'bg-purple-100',
+ label: 'Synthesis',
+ },
+ verdict: {
+ icon: 'โ๏ธ',
+ color: 'text-green-600',
+ bg: 'bg-green-100',
+ label: 'Verdict',
+ },
+};
+
+function formatTime(timestamp: string): string {
+ return new Date(timestamp).toLocaleTimeString('en-US', {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ });
+}
+
+interface StepItemProps {
+ step: DeliberationStep;
+ isLast: boolean;
+}
+
+function StepItem({ step, isLast }: StepItemProps) {
+ const [isExpanded, setIsExpanded] = useState(false);
+ const config = stepConfig[step.step_type];
+
+ return (
+
+ {!isLast && (
+
+ )}
+
+
+ {config.icon}
+
+
+
+ {isExpanded && Object.keys(step.details).length > 0 && (
+
+
+ Details
+
+
+ {JSON.stringify(step.details, null, 2)}
+
+
+ )}
+
+
+
+ );
+}
+
+export function DeliberationTimeline({ steps }: DeliberationTimelineProps) {
+ const sortedSteps = [...steps].sort((a, b) => a.sequence - b.sequence);
+
+ if (steps.length === 0) {
+ return (
+
+
No deliberation steps recorded
+
+ );
+ }
+
+ return (
+
+ {sortedSteps.map((step, index) => (
+
+ ))}
+
+ );
+}