diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx
index f4763a70..5eac2b83 100644
--- a/apps/web/src/app/[locale]/page.tsx
+++ b/apps/web/src/app/[locale]/page.tsx
@@ -28,6 +28,8 @@ import { PageTabs, type TabConfig } from '@/components/layout/page-tabs'
import { LobsterLoading } from '@/components/shared/lobster-loading'
import { ServiceTopology } from '@/components/topology'
import { BarChart3, Flame, Telescope, FlaskConical, Activity, GitBranch } from 'lucide-react'
+import { DispositionMini } from '@/components/shared/disposition-mini'
+import { RecentActivity } from '@/components/shared/recent-activity'
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
@@ -766,70 +768,78 @@ export default function Home({ params }: { params: { locale: string } }) {
{/* ── 主體 2 欄 ─────────────────────────────────────────────────────── */}
- {/* ── Feed:活躍事件 (flex:1) ───────────────────────────────────── */}
+ {/* ── 左欄 (60%): 活躍事件 + 處置統計 + 最近活動 ─────────────── */}
+
+ {/* 活躍事件 */}
-
-
- {tDashboard('activeIncidents')}
-
- {(incidents?.length ?? 0) > 0 && (
-
- {incidents?.length}
+
+
+
+ {tDashboard('activeIncidents')}
- )}
+ {(incidents?.length ?? 0) > 0 && (
+
+ {incidents?.length}
+
+ )}
+
查看全部告警 →
+
+
+
+ {isIncidentsLoading ? (
+
+ ) : incidentsError ? (
+
{incidentsError}
+ ) : (incidents?.length ?? 0) === 0 ? (
+
+
+
{tDashboard('stable')} · 0 {tDashboard('activeIncidents')}
+
+ ) : (
+
+ {incidents?.map((incident) => (
+
+ ))}
+
+ )}
+
-
- {isIncidentsLoading ? (
-
- ) : incidentsError ? (
-
{incidentsError}
- ) : (incidents?.length ?? 0) === 0 ? (
-
-
-
{tDashboard('stable')} · 0 {tDashboard('activeIncidents')}
-
- ) : (
-
- {incidents?.map((incident) => (
-
- ))}
-
- )}
-
+ {/* 處置統計迷你版 (S4) */}
+
+
+ {/* 最近活動 (S5) */}
+
+
- {/* ── Right Panel:AI + Infra (width:530px) ────────────────────── */}
+ {/* ── 右欄 (40%): OpenClaw + 基礎架構 + 監控工具 ─────────────── */}
{/* WoooClaw + Reasoning Stream */}
diff --git a/apps/web/src/components/shared/disposition-mini.tsx b/apps/web/src/components/shared/disposition-mini.tsx
new file mode 100644
index 00000000..0cbaf074
--- /dev/null
+++ b/apps/web/src/components/shared/disposition-mini.tsx
@@ -0,0 +1,106 @@
+'use client'
+
+/**
+ * DispositionMini — 處置統計迷你版 (環形圖 + 四類列表)
+ * Sprint 5R S4: 設計稿 L386-414
+ * @created 2026-04-09 Claude Opus 4.6 Asia/Taipei
+ */
+
+import { useState, useEffect } from 'react'
+import { useTranslations } from 'next-intl'
+
+const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
+
+interface DispositionSummary {
+ total: number
+ auto_rate: number
+ by_type: { auto_repair: number; human_approved: number; manual_resolved: number; cold_start_trust: number }
+}
+
+export function DispositionMini() {
+ const t = useTranslations('dashboard')
+ const [data, setData] = useState
(null)
+
+ useEffect(() => {
+ fetch(`${API_BASE}/api/v1/stats/disposition`)
+ .then(r => r.ok ? r.json() : null)
+ .then(d => { if (d?.summary) setData(d.summary) })
+ .catch(() => {})
+ }, [])
+
+ if (!data || data.total === 0) return null
+
+ const { auto_repair = 0, human_approved = 0, manual_resolved = 0, cold_start_trust = 0 } = data.by_type ?? {}
+ const total = data.total || 1
+ const pct = Math.round(data.auto_rate * 100)
+
+ // SVG 環形圖計算 (circumference = 2 * PI * 22 ≈ 138.2)
+ const C = 2 * Math.PI * 22
+ const segments = [
+ { value: auto_repair, color: '#22C55E' },
+ { value: human_approved, color: '#F59E0B' },
+ { value: manual_resolved, color: '#A855F7' },
+ { value: cold_start_trust, color: '#4A90D9' },
+ ]
+ let offset = 0
+ const arcs = segments.map(seg => {
+ const len = (seg.value / total) * C
+ const arc = { len, gap: C - len, offset: -offset, color: seg.color }
+ offset += len
+ return arc
+ })
+
+ const items = [
+ { label: t('autoRepairLabel'), value: auto_repair, color: '#22C55E' },
+ { label: t('humanApprovedLabel'), value: human_approved, color: '#F59E0B' },
+ { label: t('manualResolvedLabel'), value: manual_resolved, color: '#A855F7' },
+ { label: t('coldStartLabel'), value: cold_start_trust, color: '#4A90D9' },
+ ]
+
+ return (
+
+
+
+
{t('dispositionBreakdown')}
+
查看完整報表 →
+
+
+ {/* 環形圖 */}
+
+
+
+ {pct}%
+
+
+ {/* 列表 */}
+
+ {items.map(item => (
+
+
+ {item.label}
+ {item.value}
+
+ ))}
+
+
+
+ )
+}
diff --git a/apps/web/src/components/shared/recent-activity.tsx b/apps/web/src/components/shared/recent-activity.tsx
new file mode 100644
index 00000000..e2d55537
--- /dev/null
+++ b/apps/web/src/components/shared/recent-activity.tsx
@@ -0,0 +1,86 @@
+'use client'
+
+/**
+ * RecentActivity — 最近活動時間線
+ * Sprint 5R S5: 設計稿 L416-429
+ * @created 2026-04-09 Claude Opus 4.6 Asia/Taipei
+ */
+
+import { useState, useEffect } from 'react'
+import { useTranslations } from 'next-intl'
+
+const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
+
+interface LogEntry {
+ id: string
+ event_type: string
+ action_detail: string | null
+ actor: string | null
+ created_at: string
+}
+
+const EVENT_COLOR: Record = {
+ RESOLVED: '#22C55E',
+ EXECUTION_COMPLETED: '#22C55E',
+ ALERT_RECEIVED: '#cc2200',
+ AUTO_REPAIR_TRIGGERED: '#4A90D9',
+ TELEGRAM_SENT: '#4A90D9',
+ EXECUTION_STARTED: '#F59E0B',
+}
+
+export function RecentActivity() {
+ const t = useTranslations('dashboard')
+ const [logs, setLogs] = useState([])
+
+ useEffect(() => {
+ fetch(`${API_BASE}/api/v1/alert-operation-logs?limit=5`)
+ .then(r => r.ok ? r.json() : { items: [] })
+ .then(d => setLogs(d.items ?? []))
+ .catch(() => {})
+ }, [])
+
+ if (logs.length === 0) return null
+
+ return (
+
+
+
+
{t('activityStream')}
+
查看活動串流 →
+
+
+ {logs.map((log, i) => {
+ const time = (() => {
+ try {
+ return new Date(log.created_at).toLocaleTimeString('zh-TW', { timeZone: 'Asia/Taipei', hour: '2-digit', minute: '2-digit' })
+ } catch { return '--' }
+ })()
+ const dotColor = EVENT_COLOR[log.event_type] ?? '#87867f'
+ const detail = log.action_detail || log.event_type.replace(/_/g, ' ').toLowerCase()
+
+ return (
+
+ {time}
+
+
+ {log.actor && {log.actor}}
+ {log.actor && ' · '}
+ {detail}
+
+
+ )
+ })}
+
+
+ )
+}