# AI中心 UI 重設計 Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 將現有「戰情中心」(`/` 主頁) 重設計為「AI中心」，採用 Anthropic Warmth 米色系 + OpenClaw Blue 色彩，加入 7 節點流程管線、Q版龍蝦、NemoClaw 截圖風格動畫、完整 Sidebar 菜單。

**Architecture:** 全前端重構，後端 API 零修改。新增 3 個獨立元件 (FlowPipeline、NemoNodeAnimation、HostGrid)，重構 5 個現有檔案 (page.tsx、sidebar.tsx、header.tsx、incident-card.tsx、openclaw-panel.tsx)，廢棄 DualStateIncidentCard（授權邏輯移植後刪除）。

**Tech Stack:** Next.js 15、React、Tailwind CSS、next-intl、Zustand、pure CSS keyframes（禁止引入動畫庫）

**Spec:** `docs/superpowers/specs/2026-04-01-ai-center-redesign-design.md`
**Mockup:** `.superpowers/brainstorm/31359-1775037143/content/final-v6.html`

---

## File Map

### 新增檔案

| 檔案 | 責任 |
|------|------|
| `apps/web/src/components/incident/flow-pipeline.tsx` | 7節點水平管線 + Q版龍蝦 SVG + lobster-bob 動畫 |
| `apps/web/src/components/ai/nemo-node-animation.tsx` | NemoClaw 截圖風格 72×72 SVG（白底圓形 + 藍球 + 輻射臂 + 虛線軌道）|
| `apps/web/src/components/infra/host-grid.tsx` | 2×2 主機緊湊 grid（hostname/IP/CPU/RAM/服務列）|

### 修改檔案

| 檔案 | 修改內容 |
|------|---------|
| `apps/web/tailwind.config.ts` | 新增 Anthropic Warmth 色彩 token |
| `apps/web/src/components/layout/sidebar.tsx` | 4分區19項菜單、移除服務狀態、套用橘紅 active 樣式 |
| `apps/web/src/components/layout/header.tsx` | NemoClaw Logo SVG、P0 Banner、狀態 Pills、UTC+8 時間、Metrics Strip |
| `apps/web/src/components/incident/incident-card.tsx` | 加入 FlowPipeline、移植授權邏輯（handleApprove/handleReject/CSRF/超時重試）、套用新色彩圓角 |
| `apps/web/src/components/ai/openclaw-panel.tsx` | 替換為 NemoNodeAnimation + 思考流（Reasoning Stream）|
| `apps/web/src/app/[locale]/page.tsx` | 全面重構佈局為 3欄（Sidebar 移出、Feed flex:1、RightPanel flex:1）、移除 DataPincerPanel 容器 |
| `apps/web/messages/zh-TW.json` | `戰情中心`/`全局戰情室` → `AI中心` |
| `apps/web/messages/en.json` | `Command Center` → `AI Center` |

### 廢棄檔案（Task 8 完成後刪除）

| 檔案 | 原因 |
|------|------|
| `apps/web/src/components/incident/dual-state-incident-card.tsx` | Phase 6.5 舊風格，授權邏輯移植進新 IncidentCard 後廢棄 |

---

## Task 1: Tailwind 色彩 Token

**Files:**
- Modify: `apps/web/tailwind.config.ts`

- [ ] **Step 1: 在 tailwind.config.ts colors 區塊新增 Anthropic Warmth tokens**

找到 `colors:` → `nothing:` 區塊下方，新增：

```typescript
// ==================== Anthropic Warmth (AI中心 v6) ====================
'ai-center': {
  'bg-base':     '#f5f4ed',   // 頁面底色（米色）
  'bg-surface':  '#faf9f3',   // 表面色（暖白）
  'bg-feed':     '#f0efe8',   // 事件列底色
  border:        '#e0ddd4',   // 邊框（暖灰）
  'text-primary':  '#141413', // 主文字
  'text-secondary':'#87867f', // 次要文字
  'text-muted':  '#b0ad9f',   // 標籤文字
  accent:        '#d97757',   // 橘紅 Accent（active/hover/badge）
  'accent-light':'rgba(217,119,87,0.08)', // active 背景
},
```

- [ ] **Step 2: 確認 claw-blue token 已存在（不重複新增）**

```bash
grep -n "claw" apps/web/tailwind.config.ts
```

Expected: 看到 `claw: { blue: '#4A90D9' ... }` 已存在

- [ ] **Step 3: Commit**

```bash
cd apps/web && git add tailwind.config.ts
git commit -m "feat(design): add Anthropic Warmth ai-center color tokens"
```

---

## Task 2: FlowPipeline 元件（7節點 + Q版龍蝦）

**Files:**
- Create: `apps/web/src/components/incident/flow-pipeline.tsx`

- [ ] **Step 1: 建立元件檔案**

```typescript
'use client'

/**
 * FlowPipeline - AI中心 v6 事件流程管線
 * ========================================
 * 7節點水平管線：告警→AI偵測→AI分析→提案生成→等待授權→執行→完成
 * Q版龍蝦 SVG：進行中=橘紅 #E85530，完成=綠色 #22C55E
 * lobster-bob CSS 動畫：上下浮動 4px，1.5s loop
 */

export type FlowStage =
  | 'alert'
  | 'detection'
  | 'analysis'
  | 'proposal'
  | 'approval'
  | 'execution'
  | 'resolved'

export type NodeStatus = 'done' | 'active' | 'pending' | 'resolved'

interface FlowNode {
  id: FlowStage
  label: string
  labelKey: string
}

export interface FlowPipelineProps {
  /** 目前進行到哪個 stage */
  activeStage: FlowStage
  /** 是否已全部完成（resolved 狀態，綠色龍蝦靜止）*/
  isResolved?: boolean
  /** 各節點 tooltip 資訊（可選）*/
  tooltips?: Partial<Record<FlowStage, string>>
}

const FLOW_NODES: FlowNode[] = [
  { id: 'alert',     label: '告警',     labelKey: 'flow.alert' },
  { id: 'detection', label: 'AI偵測',   labelKey: 'flow.detection' },
  { id: 'analysis',  label: 'AI分析',   labelKey: 'flow.analysis' },
  { id: 'proposal',  label: '提案生成', labelKey: 'flow.proposal' },
  { id: 'approval',  label: '等待授權', labelKey: 'flow.approval' },
  { id: 'execution', label: '執行',     labelKey: 'flow.execution' },
  { id: 'resolved',  label: '完成',     labelKey: 'flow.resolved' },
]

/** 根據 activeStage 計算每個節點的狀態 */
function getNodeStatus(nodeId: FlowStage, activeStage: FlowStage, isResolved: boolean): NodeStatus {
  if (isResolved) return 'resolved'
  const stageOrder = FLOW_NODES.map(n => n.id)
  const nodeIndex = stageOrder.indexOf(nodeId)
  const activeIndex = stageOrder.indexOf(activeStage)
  if (nodeIndex < activeIndex) return 'done'
  if (nodeIndex === activeIndex) return 'active'
  return 'pending'
}

/** Q版龍蝦 SVG (18×20) */
function LobsterSVG({ color }: { color: string }) {
  return (
    <svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
      {/* 身體 */}
      <ellipse cx="9" cy="13" rx="5.5" ry="6.5" fill={color} />
      {/* 頭部 */}
      <circle cx="9" cy="7.5" r="4.5" fill={color} />
      {/* 眼睛 */}
      <circle cx="7" cy="6.5" r="1" fill="#b03a1a" />
      <circle cx="11" cy="6.5" r="1" fill="#b03a1a" />
      {/* 左鉗 */}
      <path d="M3.5 10 Q1 9 1.5 12 Q2 14 4 13" stroke={color} strokeWidth="1.2" fill="none" strokeLinecap="round"/>
      <ellipse cx="1.5" cy="12" rx="1.2" ry="1.5" fill={color} transform="rotate(-10 1.5 12)"/>
      {/* 右鉗 */}
      <path d="M14.5 10 Q17 9 16.5 12 Q16 14 14 13" stroke={color} strokeWidth="1.2" fill="none" strokeLinecap="round"/>
      <ellipse cx="16.5" cy="12" rx="1.2" ry="1.5" fill={color} transform="rotate(10 16.5 12)"/>
      {/* 觸鬚 */}
      <path d="M7 3 Q5 1 3 2" stroke="#b03a1a" strokeWidth="0.8" fill="none" strokeLinecap="round"/>
      <path d="M11 3 Q13 1 15 2" stroke="#b03a1a" strokeWidth="0.8" fill="none" strokeLinecap="round"/>
      {/* 尾部 */}
      <path d="M6 19 Q9 21 12 19" stroke={color} strokeWidth="1.2" fill="none" strokeLinecap="round"/>
    </svg>
  )
}

export function FlowPipeline({ activeStage, isResolved = false, tooltips }: FlowPipelineProps) {
  const lobsterColor = isResolved ? '#22C55E' : '#E85530'

  return (
    <div className="flow-pipeline-wrapper" style={{ padding: '4px 10px 8px', overflowX: 'auto' }}>
      {/* CSS keyframes via style tag */}
      <style>{`
        @keyframes lobster-bob {
          0%, 100% { transform: translateY(0); }
          50%       { transform: translateY(-4px); }
        }
        .lobster-bob { animation: lobster-bob 1.5s ease-in-out infinite; }
        .flow-tooltip {
          position: absolute;
          bottom: calc(100% + 6px);
          left: 50%;
          transform: translateX(-50%);
          background: #141413;
          color: #f5f4ed;
          font-size: 9px;
          padding: 3px 7px;
          border-radius: 4px;
          white-space: nowrap;
          pointer-events: none;
          opacity: 0;
          transition: opacity 0.15s;
          z-index: 10;
        }
        .flow-node-wrap:hover .flow-tooltip { opacity: 1; }
      `}</style>

      <div style={{ display: 'flex', alignItems: 'center', gap: 0, minWidth: 'max-content' }}>
        {FLOW_NODES.map((node, idx) => {
          const status = getNodeStatus(node.id, activeStage, isResolved)
          const isActive = status === 'active'
          const isDone = status === 'done'
          const isResNode = status === 'resolved'
          const isLast = idx === FLOW_NODES.length - 1

          // 節點顏色
          const dotBg = isDone || isResNode
            ? (isResNode ? '#22C55E' : '#E85530')
            : isActive ? '#141413' : '#f8f9fc'
          const dotBorder = isActive
            ? '1.5px solid #E85530'
            : isDone || isResNode
              ? 'none'
              : '1.5px solid #e0ddd4'
          const dotShadow = isActive ? '0 0 0 3px rgba(232,85,48,0.2)' : 'none'

          // 連接線顏色
          const connColor = isDone || isResNode ? 'rgba(232,85,48,0.5)' : '#e0ddd4'

          return (
            <div key={node.id} style={{ display: 'flex', alignItems: 'center' }}>
              <div
                className="flow-node-wrap"
                style={{ position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}
              >
                {/* Q版龍蝦（僅 active 節點顯示）*/}
                <div style={{ height: 22, display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
                  {isActive && (
                    <div className={isResolved ? '' : 'lobster-bob'}>
                      <LobsterSVG color={lobsterColor} />
                    </div>
                  )}
                </div>

                {/* 節點圓點 */}
                <div style={{
                  width: 8, height: 8,
                  borderRadius: '50%',
                  background: dotBg,
                  border: dotBorder,
                  boxShadow: dotShadow,
                  flexShrink: 0,
                }} />

                {/* 節點標籤 */}
                <span style={{
                  fontSize: 8,
                  color: isActive ? '#141413' : isDone || isResNode ? '#E85530' : '#b0ad9f',
                  fontWeight: isActive ? 700 : 400,
                  marginTop: 2,
                  whiteSpace: 'nowrap',
                }}>
                  {node.label}
                </span>

                {/* Tooltip */}
                {tooltips?.[node.id] && (
                  <div className="flow-tooltip">{tooltips[node.id]}</div>
                )}
              </div>

              {/* 連接線（最後一個節點後不需要）*/}
              {!isLast && (
                <div style={{
                  width: 16, height: 1,
                  background: connColor,
                  flexShrink: 0,
                  marginBottom: 18, // 對齊圓點位置（跳過 lobster 空間 + 標籤）
                }} />
              )}
            </div>
          )
        })}
      </div>
    </div>
  )
}

export default FlowPipeline
```

- [ ] **Step 2: 確認檔案正確建立**

```bash
ls apps/web/src/components/incident/flow-pipeline.tsx
```

Expected: 檔案存在

- [ ] **Step 3: 確認 TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | grep flow-pipeline
```

Expected: 無輸出（無錯誤）

- [ ] **Step 4: Commit**

```bash
git add apps/web/src/components/incident/flow-pipeline.tsx
git commit -m "feat(incident): add FlowPipeline 7-node pipeline with lobster animation"
```

---

## Task 3: NemoNodeAnimation 元件

**Files:**
- Create: `apps/web/src/components/ai/nemo-node-animation.tsx`

- [ ] **Step 1: 建立元件檔案**

```typescript
'use client'

/**
 * NemoNodeAnimation - NemoClaw 截圖風格節點動畫
 * ================================================
 * 72×72 SVG，白底圓形 + 漸層藍球（orb-pulse）
 * 4 主臂 + 4 斜臂 + 白色端點節點
 * 虛線旋轉軌道環（ring-spin 12s）
 * ❌ 禁止使用 openclaw-panel.tsx 的龍蝦臂 SVG
 */

export function NemoNodeAnimation({ size = 72 }: { size?: number }) {
  const id = 'nemo-node'
  return (
    <>
      <style>{`
        @keyframes orb-pulse {
          0%, 100% { transform: scale(1); opacity: 1; }
          50%       { transform: scale(1.12); opacity: 0.85; }
        }
        @keyframes ring-spin {
          from { transform: rotate(0deg); }
          to   { transform: rotate(360deg); }
        }
        .orb-pulse-anim {
          animation: orb-pulse 2s ease-in-out infinite;
          transform-origin: 36px 36px;
        }
        .ring-spin-anim {
          animation: ring-spin 12s linear infinite;
          transform-origin: 36px 36px;
        }
      `}</style>
      <svg
        width={size}
        height={size}
        viewBox="0 0 72 72"
        xmlns="http://www.w3.org/2000/svg"
      >
        <defs>
          <radialGradient id={`${id}-orbGrad`} cx="40%" cy="35%" r="60%">
            <stop offset="0%" stopColor="#7AB8F5" />
            <stop offset="100%" stopColor="#2B6CB0" />
          </radialGradient>
        </defs>

        {/* 白底圓形背景 */}
        <circle cx="36" cy="36" r="34" fill="white" stroke="#c8dcf5" strokeWidth="0.8" />

        {/* 4 主臂（solid #4A90D9，opacity 0.7）*/}
        <line x1="36" y1="36" x2="36" y2="8"  stroke="#4A90D9" strokeWidth="1.5" opacity="0.7" />
        <line x1="36" y1="36" x2="64" y2="36" stroke="#4A90D9" strokeWidth="1.5" opacity="0.7" />
        <line x1="36" y1="36" x2="36" y2="64" stroke="#4A90D9" strokeWidth="1.5" opacity="0.7" />
        <line x1="36" y1="36" x2="8"  y2="36" stroke="#4A90D9" strokeWidth="1.5" opacity="0.7" />

        {/* 4 斜臂（細，opacity 0.35）*/}
        <line x1="36" y1="36" x2="56" y2="16" stroke="#4A90D9" strokeWidth="1" opacity="0.35" />
        <line x1="36" y1="36" x2="56" y2="56" stroke="#4A90D9" strokeWidth="1" opacity="0.35" />
        <line x1="36" y1="36" x2="16" y2="56" stroke="#4A90D9" strokeWidth="1" opacity="0.35" />
        <line x1="36" y1="36" x2="16" y2="16" stroke="#4A90D9" strokeWidth="1" opacity="0.35" />

        {/* 虛線旋轉軌道環 */}
        <circle
          cx="36" cy="36" r="20"
          fill="none"
          stroke="#4A90D9"
          strokeWidth="0.8"
          strokeDasharray="3,2.5"
          opacity="0.5"
          className="ring-spin-anim"
        />

        {/* 主臂端點節點（r=3，白底+藍邊）*/}
        <circle cx="36" cy="8"  r="3" fill="white" stroke="#4A90D9" strokeWidth="1.2" />
        <circle cx="64" cy="36" r="3" fill="white" stroke="#4A90D9" strokeWidth="1.2" />
        <circle cx="36" cy="64" r="3" fill="white" stroke="#4A90D9" strokeWidth="1.2" />
        <circle cx="8"  cy="36" r="3" fill="white" stroke="#4A90D9" strokeWidth="1.2" />

        {/* 斜臂端點節點（r=2，白底+藍邊）*/}
        <circle cx="56" cy="16" r="2" fill="white" stroke="#4A90D9" strokeWidth="1" />
        <circle cx="56" cy="56" r="2" fill="white" stroke="#4A90D9" strokeWidth="1" />
        <circle cx="16" cy="56" r="2" fill="white" stroke="#4A90D9" strokeWidth="1" />
        <circle cx="16" cy="16" r="2" fill="white" stroke="#4A90D9" strokeWidth="1" />

        {/* 中央漸層藍球（orb-pulse 脈動）*/}
        <circle
          cx="36" cy="36" r="9"
          fill={`url(#${id}-orbGrad)`}
          className="orb-pulse-anim"
        />
        {/* 高光 */}
        <circle cx="33" cy="33" r="3" fill="white" opacity="0.35" />
      </svg>
    </>
  )
}

export default NemoNodeAnimation
```

- [ ] **Step 2: Commit**

```bash
git add apps/web/src/components/ai/nemo-node-animation.tsx
git commit -m "feat(ai): add NemoNodeAnimation 72x72 SVG with orb-pulse and ring-spin"
```

---

## Task 4: HostGrid 元件

**Files:**
- Create: `apps/web/src/components/infra/host-grid.tsx`

- [ ] **Step 1: 建立 infra 目錄並建立元件**

```bash
mkdir -p apps/web/src/components/infra
```

```typescript
'use client'

/**
 * HostGrid - 2×2 主機緊湊 Grid
 * ================================
 * 每格：hostname + IP + CPU/RAM + 3條關鍵服務 mini 列
 * hover 橘紅邊框，異常指標用警示色
 * 統帥鐵律：無數據顯示 "--"，禁止假數據
 */

export interface HostService {
  name: string
  healthy: boolean
}

export interface HostInfo {
  hostname: string
  ip: string
  cpuPct: number | null
  ramPct: number | null
  services: HostService[]
}

export interface HostGridProps {
  hosts: HostInfo[]
}

function MetricBar({ value, warn = 80, critical = 90 }: { value: number | null; warn?: number; critical?: number }) {
  if (value === null) return <span style={{ color: '#b0ad9f', fontSize: 9 }}>--</span>
  const color = value >= critical ? '#cc2200' : value >= warn ? '#F59E0B' : '#22C55E'
  return (
    <span style={{ color, fontSize: 9, fontWeight: 600 }}>{value}%</span>
  )
}

export function HostGrid({ hosts }: HostGridProps) {
  // 補足至 4 格（不足時顯示空格）
  const slots = [...hosts, ...Array(Math.max(0, 4 - hosts.length)).fill(null)]

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: '1fr 1fr',
      gap: 6,
      padding: '0 10px 8px',
    }}>
      {slots.map((host: HostInfo | null, idx) => (
        <div
          key={idx}
          style={{
            border: '0.5px solid #e0ddd4',
            borderRadius: 8,
            padding: '7px 9px',
            background: '#f5f4ed',
            cursor: host ? 'pointer' : 'default',
            transition: 'border-color 0.15s',
          }}
          onMouseEnter={e => host && ((e.currentTarget as HTMLDivElement).style.borderColor = '#d97757')}
          onMouseLeave={e => host && ((e.currentTarget as HTMLDivElement).style.borderColor = '#e0ddd4')}
        >
          {host ? (
            <>
              {/* Hostname + IP */}
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
                  <div style={{ width: 7, height: 7, borderRadius: '50%', background: '#22C55E', flexShrink: 0 }} />
                  <span style={{ fontSize: 9, fontWeight: 700, color: '#141413', fontFamily: 'monospace' }}>
                    {host.hostname}
                  </span>
                </div>
                <span style={{ fontSize: 8, color: '#87867f' }}>{host.ip}</span>
              </div>

              {/* CPU / RAM */}
              <div style={{ display: 'flex', gap: 8, marginBottom: 4 }}>
                <span style={{ fontSize: 8, color: '#b0ad9f' }}>
                  CPU <MetricBar value={host.cpuPct} />
                </span>
                <span style={{ fontSize: 8, color: '#b0ad9f' }}>
                  RAM <MetricBar value={host.ramPct} />
                </span>
              </div>

              {/* 3 關鍵服務 mini 列 */}
              <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                {host.services.slice(0, 3).map((svc, si) => (
                  <div key={si} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
                    <div style={{
                      width: 4, height: 4, borderRadius: '50%',
                      background: svc.healthy ? '#22C55E' : '#cc2200',
                      flexShrink: 0,
                    }} />
                    <span style={{ fontSize: 8, color: '#87867f', fontFamily: 'monospace' }}>
                      {svc.name}
                    </span>
                  </div>
                ))}
              </div>
            </>
          ) : (
            <div style={{ height: 60, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <span style={{ fontSize: 9, color: '#b0ad9f' }}>--</span>
            </div>
          )}
        </div>
      ))}
    </div>
  )
}

export default HostGrid
```

- [ ] **Step 2: Commit**

```bash
git add apps/web/src/components/infra/host-grid.tsx
git commit -m "feat(infra): add HostGrid 2x2 compact host grid component"
```

---

## Task 5: Sidebar 重構

**Files:**
- Modify: `apps/web/src/components/layout/sidebar.tsx`

**注意：修改前必讀檔案頂部所有註解（feedback_read_comments_first 鐵律）**

- [ ] **Step 1: 讀取現有 sidebar.tsx 完整內容**

```bash
cat apps/web/src/components/layout/sidebar.tsx
```

- [ ] **Step 2: 替換 NAV_ITEMS 常數（4分區）**

把現有的 `NAV_ITEMS` 陣列和相關 icons import 全部替換為：

```typescript
import {
  LayoutDashboard, ShieldCheck, Bell, Monitor, Activity,
  Bug, GitBranch, Shield, ClipboardCheck,
  Wrench, Package, Ticket, DollarSign, Zap, FileText,
  BookOpen, Terminal, AppWindow, Server,
  Users, BellRing, CreditCard, HelpCircle, Settings,
} from 'lucide-react'

// ============================================================
// AI中心 v6 — 4分區 Sidebar（統帥批准 2026-04-01）
// ============================================================

type NavSection = {
  sectionKey: string
  sectionLabel: string
  items: NavItemConfig[]
}

const NAV_SECTIONS: NavSection[] = [
  {
    sectionKey: 'ai-core',
    sectionLabel: 'AI 核心',
    items: [
      { id: 'ai-center',      href: '/',               labelKey: 'dashboard',     Icon: LayoutDashboard },
      { id: 'authorizations', href: '/authorizations',  labelKey: 'approvals',     Icon: ShieldCheck, badge: true },
      { id: 'alerts',         href: '/alerts',          labelKey: 'alerts',        Icon: Bell },
    ],
  },
  {
    sectionKey: 'monitoring',
    sectionLabel: '監控與安全',
    items: [
      { id: 'monitoring',  href: '/monitoring',  labelKey: 'monitoring',  Icon: Monitor },
      { id: 'apm',         href: '/apm',         labelKey: 'apm',         Icon: Activity },
      { id: 'errors',      href: '/errors',      labelKey: 'errors',      Icon: Bug },
      { id: 'topology',    href: '/topology',    labelKey: 'topology',    Icon: GitBranch },
      { id: 'security',    href: '/security',    labelKey: 'security',    Icon: Shield },
      { id: 'compliance',  href: '/compliance',  labelKey: 'compliance',  Icon: ClipboardCheck },
    ],
  },
  {
    sectionKey: 'ops',
    sectionLabel: '運維管理',
    items: [
      { id: 'auto-repair',  href: '/auto-repair',  labelKey: 'autoRepair',  Icon: Wrench },
      { id: 'deployments',  href: '/deployments',  labelKey: 'deployments', Icon: Package },
      { id: 'tickets',      href: '/tickets',      labelKey: 'tickets',     Icon: Ticket },
      { id: 'cost',         href: '/cost',         labelKey: 'cost',        Icon: DollarSign },
      { id: 'action-logs',  href: '/action-logs',  labelKey: 'actions',     Icon: Zap },
      { id: 'reports',      href: '/reports',      labelKey: 'reports',     Icon: FileText },
    ],
  },
  {
    sectionKey: 'knowledge',
    sectionLabel: '知識與工具',
    items: [
      { id: 'knowledge-base', href: '/knowledge-base', labelKey: 'knowledge', Icon: BookOpen },
      { id: 'terminal',       href: '/terminal',       labelKey: 'terminal',  Icon: Terminal },
      { id: 'apps',           href: '/apps',           labelKey: 'apps',      Icon: AppWindow },
      { id: 'services',       href: '/services',       labelKey: 'services',  Icon: Server },
    ],
  },
]

const BOTTOM_NAV_ITEMS: NavItemConfig[] = [
  { id: 'users',         href: '/users',         labelKey: 'users',         Icon: Users },
  { id: 'notifications', href: '/notifications', labelKey: 'notifications', Icon: BellRing },
  { id: 'billing',       href: '/billing',       labelKey: 'billing',       Icon: CreditCard },
  { id: 'help',          href: '/help',          labelKey: 'help',          Icon: HelpCircle },
  { id: 'settings',      href: '/settings',      labelKey: 'settings',      Icon: Settings },
]
```

- [ ] **Step 3: 替換 sidebar 渲染邏輯**

找到 sidebar 的主要渲染區塊，把原本的 `NAV_ITEMS.map(...)` 替換為分區渲染。在 `<nav>` 裡改為：

```tsx
{/* 4 分區菜單 */}
{NAV_SECTIONS.map(section => (
  <div key={section.sectionKey} className="mb-2">
    {/* 分區標題（collapsed 時隱藏）*/}
    {!collapsed && (
      <div style={{
        fontSize: 8,
        color: '#b0ad9f',
        letterSpacing: '1.5px',
        textTransform: 'uppercase',
        padding: '8px 12px 3px',
        fontFamily: 'monospace',
      }}>
        {section.sectionLabel}
      </div>
    )}
    {section.items.map(item => {
      const isActive = pathname === `/${locale}${item.href === '/' ? '' : item.href}`
      const pendingCount = item.badge ? pendingApprovals : 0
      return (
        <Link
          key={item.id}
          href={`/${locale}${item.href}`}
          style={{
            display: 'flex',
            alignItems: 'center',
            gap: collapsed ? 0 : 8,
            justifyContent: collapsed ? 'center' : 'flex-start',
            padding: collapsed ? '8px 0' : '6px 12px',
            fontSize: 11,
            color: isActive ? '#141413' : '#87867f',
            fontWeight: isActive ? 600 : 400,
            borderRight: isActive ? '2px solid #d97757' : '2px solid transparent',
            background: isActive ? 'rgba(217,119,87,0.08)' : 'transparent',
            textDecoration: 'none',
            transition: 'all 0.15s',
            position: 'relative',
          }}
        >
          <item.Icon size={14} />
          {!collapsed && <span>{t(item.labelKey)}</span>}
          {item.badge && pendingCount > 0 && (
            <span style={{
              marginLeft: 'auto',
              fontSize: 8,
              background: '#d97757',
              color: 'white',
              borderRadius: 10,
              padding: '1px 5px',
              fontWeight: 700,
            }}>
              {pendingCount}
            </span>
          )}
        </Link>
      )
    })}
  </div>
))}

{/* Separator */}
<div style={{ borderTop: '0.5px solid #e0ddd4', margin: '6px 0' }} />

{/* 底部菜單 */}
{BOTTOM_NAV_ITEMS.map(item => {
  const isActive = pathname === `/${locale}${item.href}`
  return (
    <Link
      key={item.id}
      href={`/${locale}${item.href}`}
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: collapsed ? 0 : 8,
        justifyContent: collapsed ? 'center' : 'flex-start',
        padding: collapsed ? '7px 0' : '5px 12px',
        fontSize: 10,
        color: isActive ? '#141413' : '#87867f',
        fontWeight: isActive ? 600 : 400,
        borderRight: isActive ? '2px solid #d97757' : '2px solid transparent',
        background: isActive ? 'rgba(217,119,87,0.08)' : 'transparent',
        textDecoration: 'none',
        transition: 'all 0.15s',
      }}
    >
      <item.Icon size={12} />
      {!collapsed && <span>{t(item.labelKey)}</span>}
    </Link>
  )
})}
```

- [ ] **Step 4: 確認 TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | grep sidebar
```

Expected: 無輸出（無錯誤）

- [ ] **Step 5: Commit**

```bash
git add apps/web/src/components/layout/sidebar.tsx
git commit -m "feat(sidebar): 4-section nav with AI center active style #d97757"
```

---

## Task 6: i18n 更新

**Files:**
- Modify: `apps/web/messages/zh-TW.json`
- Modify: `apps/web/messages/en.json`

- [ ] **Step 1: 更新 zh-TW.json**

找到 `"title": "全局戰情室"` → 改為 `"title": "AI中心"`

找到 `"tagline": "智能戰情室"` → 改為 `"tagline": "AI 指揮操作介面"`

找到 `"liveDashboard": "即時戰情室 (SSE)"` → 改為 `"liveDashboard": "即時事件流 (SSE)"`

並新增以下 flow pipeline i18n keys（在 `dashboard` 或 `incident` 區塊下新增 `flow` 子物件）：

```json
"flow": {
  "alert":     "告警",
  "detection": "AI偵測",
  "analysis":  "AI分析",
  "proposal":  "提案生成",
  "approval":  "等待授權",
  "execution": "執行",
  "resolved":  "完成"
}
```

- [ ] **Step 2: 更新 en.json**

找到 `"title": "Command Center"` → 改為 `"title": "AI Center"`

新增對應 flow keys：

```json
"flow": {
  "alert":     "Alert",
  "detection": "AI Detection",
  "analysis":  "AI Analysis",
  "proposal":  "Proposal",
  "approval":  "Awaiting Auth",
  "execution": "Execution",
  "resolved":  "Resolved"
}
```

- [ ] **Step 3: Commit**

```bash
git add apps/web/messages/zh-TW.json apps/web/messages/en.json
git commit -m "feat(i18n): rename to AI Center, add flow pipeline keys"
```

---

## Task 7: IncidentCard 重構（移植授權邏輯 + 新視覺）

**Files:**
- Modify: `apps/web/src/components/incident/incident-card.tsx`

**注意：授權邏輯從 `dual-state-incident-card.tsx` 移植，務必對照該檔案的 `handleApprove` / `handleReject` 實作**

- [ ] **Step 1: 讀取現有 incident-card.tsx 和 dual-state-incident-card.tsx**

```bash
cat apps/web/src/components/incident/incident-card.tsx
cat apps/web/src/components/incident/dual-state-incident-card.tsx
```

- [ ] **Step 2: 完整替換 incident-card.tsx**

以下為新版 IncidentCard，融合 AI中心視覺 + 授權邏輯：

```typescript
'use client'

/**
 * IncidentCard - AI中心 v6 事件卡片
 * =====================================
 * Anthropic Warmth 米色系 + OpenClaw Blue
 * - 頂邊條顏色 = 嚴重度（P0=紅/P2=橙/P3=藍/OK=綠）
 * - FlowPipeline 7節點 + Q版龍蝦
 * - 授權邏輯從 DualStateIncidentCard (Phase 6.5) 移植
 *   handleApprove / handleReject / CSRF / 30s 超時重試
 *
 * 統帥鐵律: 禁止假數據！無數據顯示 "--"
 * @deprecated dual-state-incident-card.tsx (Phase 6.5 舊風格，此元件取代後刪除)
 */

import React, { useState, useCallback, useRef, useEffect } from 'react'
import { useTranslations } from 'next-intl'
import type { IncidentResponse, DecisionInfo } from '@/lib/api-client'
import { apiClient } from '@/lib/api-client'
import { useCSRF } from '@/hooks/useCSRF'
import { FlowPipeline, type FlowStage } from './flow-pipeline'

// =============================================================================
// Types
// =============================================================================

type ButtonState = 'idle' | 'loading' | 'approved' | 'rejected' | 'error' | 'timeout'

const EXECUTION_TIMEOUT_MS = 30000

export interface IncidentCardProps {
  incident: IncidentResponse
  decision?: DecisionInfo | null
  onApprovalChange?: (proposalId: string, newStatus: 'approved' | 'rejected') => void
}

// =============================================================================
// Severity / Status Config
// =============================================================================

const SEV_CONFIG = {
  P0: { barColor: '#cc2200', label: 'P0', labelBg: '#ffeaea', labelColor: '#cc2200' },
  P1: { barColor: '#d97757', label: 'P1', labelBg: '#fff0ea', labelColor: '#b05030' },
  P2: { barColor: '#4A90D9', label: 'P2', labelBg: '#eaf4ff', labelColor: '#2a6cb0' },
  P3: { barColor: '#22C55E', label: 'P3', labelBg: '#eafff2', labelColor: '#166534' },
} as const

/** 根據 incident status 對應 FlowStage */
function toFlowStage(status: string, severity: string): FlowStage {
  switch (status) {
    case 'new':                 return 'alert'
    case 'investigating':       return 'detection'
    case 'analyzing':           return 'analysis'
    case 'proposal_generated':  return 'proposal'
    case 'waiting_approval':    return 'approval'
    case 'executing':           return 'execution'
    case 'resolved':            return 'resolved'
    default:                    return severity === 'P0' ? 'alert' : 'detection'
  }
}

/** 格式化持續時間 */
function formatDuration(createdAt: string | undefined): string {
  if (!createdAt) return '--'
  try {
    const ms = Date.now() - new Date(createdAt).getTime()
    const mins = Math.floor(ms / 60000)
    if (mins < 60) return `${mins}m`
    return `${Math.floor(mins / 60)}h${mins % 60}m`
  } catch {
    return '--'
  }
}

// =============================================================================
// Component
// =============================================================================

export function IncidentCard({ incident, decision, onApprovalChange }: IncidentCardProps) {
  const t = useTranslations('incident.card')
  const { csrfToken } = useCSRF()

  const [buttonState, setButtonState] = useState<ButtonState>('idle')
  const [errorMessage, setErrorMessage] = useState<string | null>(null)
  const [currentProposalId, setCurrentProposalId] = useState<string | null>(null)
  const [aiExpanded, setAiExpanded] = useState(false)
  const timeoutRef = useRef<NodeJS.Timeout | null>(null)

  useEffect(() => {
    return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current) }
  }, [])

  const sev = incident.severity as keyof typeof SEV_CONFIG
  const sevCfg = SEV_CONFIG[sev] ?? SEV_CONFIG.P3
  const flowStage = toFlowStage(incident.status, incident.severity)
  const isResolved = incident.status === 'resolved'
  const isWaitingApproval = incident.status === 'waiting_approval'

  const serviceName = incident.affected_services?.[0] ?? '--'
  const duration = formatDuration(incident.created_at)
  const isDecisionReady = decision?.state === 'ready' || !!currentProposalId
  const isAnalyzing = decision?.state === 'analyzing'
  const decisionAction = decision?.proposal_data?.action ?? ''
  const decisionReasoning = decision?.proposal_data?.reasoning ?? ''

  // ── handleApprove（移植自 DualStateIncidentCard Phase 6.5）──────────────
  const handleApprove = useCallback(async () => {
    if (!isDecisionReady || buttonState === 'loading') return
    setButtonState('loading')
    setErrorMessage(null)
    if (timeoutRef.current) clearTimeout(timeoutRef.current)
    timeoutRef.current = setTimeout(() => {
      setButtonState('timeout')
      setErrorMessage(t('timeoutMessage'))
    }, EXECUTION_TIMEOUT_MS)

    try {
      let approvalId = currentProposalId
      if (!approvalId && decision?.token) {
        const proposalResult = await apiClient.generateProposal(incident.incident_id)
        if (!proposalResult.success || !proposalResult.proposal) {
          throw new Error(proposalResult.message || 'Failed to generate proposal')
        }
        approvalId = proposalResult.proposal.id
        setCurrentProposalId(approvalId)
      }
      if (!approvalId) throw new Error('No approval ID available')
      const result = await apiClient.signApproval(approvalId, 'commander', 'Authorized via AI Center', csrfToken)
      if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null }
      const approvalStatus = result.approval?.status?.toLowerCase()
      if (approvalStatus === 'approved') {
        setButtonState('approved')
        onApprovalChange?.(approvalId, 'approved')
      } else {
        setButtonState('idle')
      }
    } catch (error) {
      if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null }
      setButtonState('error')
      setErrorMessage(error instanceof Error ? error.message : String(error))
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentProposalId, decision, incident.incident_id, isDecisionReady, buttonState, onApprovalChange, csrfToken])

  // ── handleReject（移植自 DualStateIncidentCard Phase 6.5）────────────────
  const handleReject = useCallback(async () => {
    if (!isDecisionReady || buttonState === 'loading') return
    setButtonState('loading')
    setErrorMessage(null)
    if (timeoutRef.current) clearTimeout(timeoutRef.current)
    timeoutRef.current = setTimeout(() => {
      setButtonState('timeout')
      setErrorMessage(t('timeoutMessage'))
    }, EXECUTION_TIMEOUT_MS)

    try {
      let approvalId = currentProposalId
      if (!approvalId && decision?.token) {
        const proposalResult = await apiClient.generateProposal(incident.incident_id)
        if (proposalResult.success && proposalResult.proposal) {
          approvalId = proposalResult.proposal.id
          setCurrentProposalId(approvalId)
        }
      }
      if (!approvalId) throw new Error('No approval ID available')
      await apiClient.rejectApproval(approvalId, 'Rejected via AI Center', csrfToken)
      if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null }
      setButtonState('rejected')
      onApprovalChange?.(approvalId, 'rejected')
    } catch (error) {
      if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null }
      setButtonState('error')
      setErrorMessage(error instanceof Error ? error.message : String(error))
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentProposalId, decision, incident.incident_id, isDecisionReady, buttonState, onApprovalChange, csrfToken])

  // ── 授權按鈕渲染 ───────────────────────────────────────────────────────────
  const renderApproveButtons = () => {
    switch (buttonState) {
      case 'loading':
        return (
          <span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: '#87867f' }}>
            <span style={{ width: 8, height: 8, border: '1.5px solid #e0ddd4', borderTopColor: '#d97757', borderRadius: '50%', animation: 'spin 0.8s linear infinite', display: 'inline-block' }} />
            {t('executing')}
          </span>
        )
      case 'approved':
        return <span style={{ fontSize: 9, color: '#22C55E', fontWeight: 700 }}>✓ {t('approved')}</span>
      case 'rejected':
        return <span style={{ fontSize: 9, color: '#cc2200', fontWeight: 700 }}>✗ {t('rejected')}</span>
      case 'error':
      case 'timeout':
        return (
          <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <span style={{ fontSize: 9, color: buttonState === 'timeout' ? '#F59E0B' : '#cc2200' }}>
              {buttonState === 'timeout' ? t('timeout') : t('error')}
            </span>
            <button
              onClick={() => { setButtonState('idle'); setErrorMessage(null) }}
              style={{ fontSize: 9, padding: '2px 7px', background: '#f0efe8', border: '0.5px solid #e0ddd4', borderRadius: 10, cursor: 'pointer', color: '#87867f' }}
            >
              {t('retry')}
            </button>
          </div>
        )
      default:
        return (
          <div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
            <button
              onClick={handleApprove}
              disabled={!isDecisionReady}
              style={{
                padding: '5px 16px',
                background: '#141413',
                color: '#f5f4ed',
                border: 'none',
                borderRadius: 20,
                fontSize: 10,
                cursor: isDecisionReady ? 'pointer' : 'not-allowed',
                opacity: isDecisionReady ? 1 : 0.4,
                fontFamily: 'inherit',
              }}
              title={isAnalyzing ? t('analyzing') : decisionAction || t('authorizeExecution')}
            >
              ✓ {t('authorize')}
            </button>
            <button
              onClick={handleReject}
              disabled={!isDecisionReady}
              style={{
                padding: '5px 16px',
                background: 'transparent',
                color: '#87867f',
                border: '0.5px solid #e0ddd4',
                borderRadius: 20,
                fontSize: 10,
                cursor: isDecisionReady ? 'pointer' : 'not-allowed',
                opacity: isDecisionReady ? 1 : 0.4,
                fontFamily: 'inherit',
              }}
            >
              ✗ {t('reject')}
            </button>
            {isAnalyzing && <span style={{ fontSize: 9, color: '#F59E0B' }}>⏳</span>}
          </div>
        )
    }
  }

  return (
    <div style={{
      background: '#faf9f3',
      border: '0.5px solid #e0ddd4',
      borderRadius: 8,
      overflow: 'hidden',
      marginBottom: 8,
    }}>
      {/* 頂邊條 3px */}
      <div style={{ height: 3, background: sevCfg.barColor, borderRadius: '8px 8px 0 0' }} />

      {/* 標頭列：嚴重度 + 服務標籤 + 時間 */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 10px 4px' }}>
        <span style={{
          fontSize: 9, fontWeight: 700, padding: '2px 8px',
          background: sevCfg.labelBg, color: sevCfg.labelColor,
          borderRadius: 10, flexShrink: 0,
        }}>
          {sevCfg.label}
        </span>
        <span style={{
          fontSize: 9, color: '#87867f', background: '#ece9e0',
          border: '0.5px solid #dedad0', padding: '2px 7px', borderRadius: 10,
        }}>
          {serviceName}
        </span>
        <span style={{ marginLeft: 'auto', fontSize: 9, color: '#b0ad9f' }}>
          {duration}
        </span>
      </div>

      {/* 事件標題 */}
      <div style={{ padding: '2px 10px 4px', fontSize: 11, fontWeight: 600, color: '#141413', lineHeight: 1.4 }}>
        {incident.description ?? '--'}
      </div>

      {/* INC-ID */}
      <div style={{ padding: '0 10px 4px', fontSize: 9, color: '#b0ad9f', fontFamily: 'monospace' }}>
        {incident.incident_id}
      </div>

      {/* 流程狀態圖 */}
      <FlowPipeline activeStage={flowStage} isResolved={isResolved} />

      {/* Impact 指標列 */}
      <div style={{
        margin: '0 10px 6px',
        padding: '5px 8px',
        background: '#f0efe8',
        border: '0.5px solid #e0ddd4',
        borderRadius: 6,
        display: 'flex',
        gap: 12,
        flexWrap: 'wrap' as const,
      }}>
        <span style={{ fontSize: 9, color: '#87867f' }}>
          影響服務 <strong style={{ color: '#141413' }}>{incident.affected_services?.length ?? 0}</strong>
        </span>
        <span style={{ fontSize: 9, color: '#87867f' }}>
          信號數 <strong style={{ color: '#141413' }}>{incident.signal_count ?? '--'}</strong>
        </span>
        <span style={{ fontSize: 9, color: '#87867f' }}>
          狀態 <strong style={{ color: '#141413' }}>{incident.status}</strong>
        </span>
      </div>

      {/* AI 提案行（可展開）*/}
      {decisionAction && (
        <>
          <div
            onClick={() => setAiExpanded(v => !v)}
            style={{
              margin: '0 10px 6px',
              padding: '5px 8px',
              background: '#f0efe8',
              border: '0.5px solid #e0ddd4',
              borderLeft: '3px solid rgba(217,119,87,0.5)',
              borderRadius: 6,
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              gap: 5,
              fontSize: 9,
              color: '#87867f',
            }}
          >
            <span style={{ color: '#d97757' }}>▶</span>
            <span>AI 提案: {decisionAction.slice(0, 50)}{decisionAction.length > 50 ? '...' : ''}</span>
            <span style={{ marginLeft: 'auto' }}>{aiExpanded ? '▲' : '▼'}</span>
          </div>
          {aiExpanded && (
            <div style={{
              margin: '0 10px 6px',
              padding: '8px',
              background: '#f0efe8',
              border: '0.5px solid #e0ddd4',
              borderLeft: '3px solid #d97757',
              borderRadius: 6,
              fontSize: 9,
              color: '#87867f',
            }}>
              <div style={{ color: '#141413', marginBottom: 4 }}>{decisionAction}</div>
              {decisionReasoning && (
                <div style={{ color: '#87867f', fontStyle: 'italic' }}>
                  💡 {decisionReasoning.slice(0, 150)}{decisionReasoning.length > 150 ? '...' : ''}
                </div>
              )}
            </div>
          )}
        </>
      )}

      {/* 授權按鈕（僅 waiting_approval 狀態）*/}
      {isWaitingApproval && (
        <div style={{ padding: '6px 10px 10px', display: 'flex', justifyContent: 'flex-end' }}>
          {renderApproveButtons()}
          {errorMessage && (
            <span style={{ fontSize: 8, color: '#cc2200', marginLeft: 6, maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={errorMessage}>
              {errorMessage}
            </span>
          )}
        </div>
      )}

      {/* spin keyframe */}
      <style>{`@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }`}</style>
    </div>
  )
}

export default IncidentCard
```

- [ ] **Step 3: 確認 TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | grep -E "incident-card|flow-pipeline"
```

Expected: 無輸出

- [ ] **Step 4: Commit**

```bash
git add apps/web/src/components/incident/incident-card.tsx
git commit -m "feat(incident): redesign IncidentCard with FlowPipeline, migrate auth logic from DualState"
```

---

## Task 8: 廢棄 DualStateIncidentCard

**Files:**
- Modify: `apps/web/src/components/incident/index.ts`
- Delete: `apps/web/src/components/incident/dual-state-incident-card.tsx`

**前提：Task 7 完成且測試通過**

- [ ] **Step 1: 確認 DualStateIncidentCard 沒有其他 import 路徑**

```bash
grep -rn "DualStateIncidentCard\|dual-state-incident-card" apps/web/src/ --include="*.tsx" --include="*.ts"
```

Expected: 只剩 `incident/index.ts` 和 `incident/dual-state-incident-card.tsx` 本身

- [ ] **Step 2: 從 index.ts 移除 DualStateIncidentCard export**

讀取 `apps/web/src/components/incident/index.ts`，找到：
```typescript
export { DualStateIncidentCard } from './dual-state-incident-card'
// 或類似的 export
```
將這行刪除。

- [ ] **Step 3: 刪除元件檔案**

```bash
git rm apps/web/src/components/incident/dual-state-incident-card.tsx
```

- [ ] **Step 4: 確認 TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | head -20
```

Expected: 無錯誤

- [ ] **Step 5: Commit**

```bash
git add apps/web/src/components/incident/index.ts
git commit -m "chore(incident): remove DualStateIncidentCard (Phase 6.5 old style, migrated to IncidentCard)"
```

---

## Task 9: openclaw-panel.tsx 替換為 NemoNodeAnimation + Reasoning Stream

**Files:**
- Modify: `apps/web/src/components/ai/openclaw-panel.tsx`

- [ ] **Step 1: 讀取現有 openclaw-panel.tsx 完整內容**

```bash
cat apps/web/src/components/ai/openclaw-panel.tsx
```

- [ ] **Step 2: 替換 AI Panel 視覺區塊**

找到 `OpenClawPanel` 元件的 SVG 渲染區塊（龍蝦臂 SVG），替換為：

```tsx
import { NemoNodeAnimation } from './nemo-node-animation'

// 在元件 JSX 內，把原本的 SVG 龍蝦臂區塊替換為：
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '10px 12px' }}>
  {/* NemoClaw 截圖風格動畫 */}
  <NemoNodeAnimation size={72} />

  {/* 右側文字 */}
  <div style={{ flex: 1 }}>
    <div style={{ fontSize: 11, fontWeight: 700, color: '#141413', marginBottom: 2 }}>
      NemoClaw
    </div>
    <div style={{ fontSize: 9, color: '#87867f', marginBottom: 6 }}>
      {STATUS_MESSAGES[status]}
    </div>
    <span style={{
      fontSize: 8, padding: '2px 8px',
      background: 'rgba(74,144,217,0.08)',
      border: '0.5px solid rgba(74,144,217,0.3)',
      borderRadius: 10,
      color: '#4A90D9',
    }}>
      OpenClaw Pipeline
    </span>
  </div>
</div>
```

- [ ] **Step 3: 保留 Reasoning Stream（thinking steps）**

確認元件仍保留思考流渲染（最多 8 條，`✓`/`▶`/`○` 狀態），橘紅 `▶` 顏色改為 `#d97757`。

- [ ] **Step 4: 確認 TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | grep openclaw
```

Expected: 無輸出

- [ ] **Step 5: Commit**

```bash
git add apps/web/src/components/ai/openclaw-panel.tsx
git commit -m "feat(ai): replace dragon arm SVG with NemoNodeAnimation in OpenClawPanel"
```

---

## Task 10: 主頁 page.tsx 佈局重構

**Files:**
- Modify: `apps/web/src/app/[locale]/page.tsx`

**這是最大的重構任務，修改前務必讀完整個 page.tsx**

- [ ] **Step 1: 讀取完整 page.tsx**

```bash
cat apps/web/src/app/\[locale\]/page.tsx
```

- [ ] **Step 2: 替換 import 區塊**

把 `DualStateIncidentCard` 改為 `IncidentCard`，並移除不再需要的舊元件：

```typescript
// 移除:
// import { ThinkingTerminal, DualStateIncidentCard } from '@/components/incident'
// import { DataPincerPanel } from '@/components/cyber'
// import { GlobalPulseChart } from '@/components/charts/global-pulse-chart'
// import { OpenClawStateMachine } from '@/components/ai/openclaw-state-machine'

// 新增:
import { IncidentCard } from '@/components/incident'
import { OpenClawPanel } from '@/components/ai/openclaw-panel'
import { HostGrid, type HostInfo } from '@/components/infra/host-grid'
import type { DecisionInfo } from '@/lib/api-client'
```

- [ ] **Step 3: 移除 `mapToDualState` 和 `convertToDecisionChain` 工具函數**

這兩個函數是 DualStateIncidentCard 專用的，刪除整個函數定義（約 line 58–196）。

- [ ] **Step 4: 替換主頁佈局 JSX**

把 `return (...)` 裡的內容完全替換為新版 3欄佈局：

```tsx
return (
  <div style={{
    display: 'flex',
    flexDirection: 'column',
    height: '100vh',
    background: '#f5f4ed',
    fontFamily: 'Inter, system-ui, sans-serif',
    overflow: 'hidden',
  }}>
    {/* ── Metrics Strip (50px) ────────────────────────────────────── */}
    <div style={{
      background: '#faf9f3',
      borderBottom: '0.5px solid #e0ddd4',
      height: 50,
      padding: '0 20px',
      display: 'flex',
      alignItems: 'center',
      flexShrink: 0,
      gap: 0,
    }}>
      {[
        { label: '活躍事件', value: incidents?.length ?? '--', sub: incidents?.filter(i => i.severity === 'P0').length ? `+${incidents.filter(i => i.severity === 'P0').length} P0` : '穩定' },
        { label: '服務健康', value: `${pulseMetrics?.length ?? '--'}/${pulseMetrics?.length ?? '--'}`, sub: '正常' },
        { label: '待簽核', value: pendingApprovals ?? '--', sub: '等待授權' },
        { label: '今日事件', value: incidents?.length ?? '--', sub: '' },
        { label: '自動處置率', value: '--', sub: '' },
        { label: 'MTTR 均值', value: '--', sub: '' },
      ].map((m, i, arr) => (
        <div key={i} style={{
          flex: 1,
          display: 'flex',
          flexDirection: 'column',
          gap: 1,
          paddingRight: 14,
          borderRight: i < arr.length - 1 ? '0.5px solid #e8e5dc' : 'none',
          marginRight: i < arr.length - 1 ? 14 : 0,
        }}>
          <span style={{ fontSize: 8, color: '#b0ad9f', letterSpacing: '2px', textTransform: 'uppercase' }}>
            {m.label}
          </span>
          <span style={{ fontSize: 16, fontWeight: 700, color: '#141413', lineHeight: 1.1, fontFamily: 'monospace' }}>
            {m.value}
          </span>
          {m.sub && <span style={{ fontSize: 8, color: '#87867f' }}>{m.sub}</span>}
        </div>
      ))}
    </div>

    {/* ── 主體 3 欄 ────────────────────────────────────────────────── */}
    <div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>

      {/* ── Feed：活躍事件（flex:1）──────────────────────────────── */}
      <div style={{
        flex: 1,
        borderRight: '0.5px solid #e0ddd4',
        overflow: 'hidden',
        display: 'flex',
        flexDirection: 'column',
      }}>
        {/* Feed 標題列 */}
        <div style={{
          padding: '8px 12px',
          borderBottom: '0.5px solid #e0ddd4',
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          flexShrink: 0,
          background: '#faf9f3',
        }}>
          <span style={{ fontSize: 10, fontWeight: 700, color: '#141413', letterSpacing: '1px', textTransform: 'uppercase', fontFamily: 'monospace' }}>
            活躍事件
          </span>
          {(incidents?.length ?? 0) > 0 && (
            <span style={{
              fontSize: 9, background: 'rgba(217,119,87,0.1)', color: '#a04010',
              padding: '2px 8px', fontWeight: 700,
              border: '0.5px solid rgba(217,119,87,0.25)', borderRadius: 10,
            }}>
              {incidents?.length}
            </span>
          )}
        </div>

        {/* Feed 內容 */}
        <div style={{ flex: 1, overflowY: 'auto', padding: '8px 10px' }}>
          {isIncidentsLoading ? (
            <div style={{ display: 'flex', justifyContent: 'center', padding: 32 }}>
              <span style={{ fontSize: 10, color: '#b0ad9f' }}>載入中...</span>
            </div>
          ) : incidentsError ? (
            <div style={{ padding: 16, fontSize: 10, color: '#cc2200' }}>{incidentsError}</div>
          ) : (incidents?.length ?? 0) === 0 ? (
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 48, gap: 8 }}>
              <div style={{ width: 8, height: 8, borderRadius: '50%', background: '#22C55E' }} />
              <span style={{ fontSize: 10, color: '#87867f' }}>系統穩定 · 0 活躍事件</span>
            </div>
          ) : (
            incidents?.map(incident => (
              <IncidentCard
                key={incident.incident_id}
                incident={incident}
                decision={incident.decision}
              />
            ))
          )}
        </div>
      </div>

      {/* ── Right Panel：AI + Infra（flex:1）─────────────────────── */}
      <div style={{
        flex: 1,
        overflow: 'hidden',
        display: 'flex',
        flexDirection: 'column',
      }}>
        <div style={{ flex: 1, overflowY: 'auto' }}>

          {/* 7.1 NemoClaw + Reasoning Stream */}
          <div style={{ borderBottom: '0.5px solid #e0ddd4' }}>
            <div style={{
              padding: '8px 12px',
              borderBottom: '0.5px solid #e0ddd4',
              fontSize: 10, fontWeight: 700, color: '#141413',
              letterSpacing: '1px', textTransform: 'uppercase',
              fontFamily: 'monospace', background: '#faf9f3',
            }}>
              OpenClaw 認知引擎
            </div>
            <OpenClawPanel
              status={
                (incidents?.length ?? 0) > 0 ? 'analyzing' : 'patrolling'
              }
            />
          </div>

          {/* 7.3 基礎架構 2×2 Grid */}
          <div style={{ borderBottom: '0.5px solid #e0ddd4' }}>
            <div style={{
              padding: '8px 12px',
              borderBottom: '0.5px solid #e0ddd4',
              fontSize: 10, fontWeight: 700, color: '#141413',
              letterSpacing: '1px', textTransform: 'uppercase',
              fontFamily: 'monospace', background: '#faf9f3',
            }}>
              基礎架構
            </div>
            <HostGrid hosts={[]} />
          </div>

        </div>
      </div>
    </div>
  </div>
)
```

**注意：** `pendingApprovals` 需從 `useIncidents` hook 或 `useApprovalStore` 取得。確認現有 hook 已有此值（`apps/web/src/hooks/useIncidents.ts` 已返回 `pendingApprovals`）。

- [ ] **Step 5: 確認 TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | head -30
```

Expected: 無錯誤或只有不影響執行的 warning

- [ ] **Step 6: Commit**

```bash
git add apps/web/src/app/\[locale\]/page.tsx
git commit -m "feat(page): redesign AI Center layout - 3-col Feed+RightPanel, Metrics Strip"
```

---

## Task 11: header.tsx 更新（NemoClaw Logo + Metrics 已移至 page.tsx）

**Files:**
- Modify: `apps/web/src/components/layout/header.tsx`

- [ ] **Step 1: 讀取現有 header.tsx**

```bash
cat apps/web/src/components/layout/header.tsx
```

- [ ] **Step 2: 更新 header 左側 Logo 區塊**

找到 header 左側 logo 渲染，替換為含 NemoClaw SVG 的區塊：

```tsx
{/* 左側 Logo */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
  {/* NemoClaw Logo SVG (34×34，viewBox 0 0 140 140，深色底) */}
  <div style={{
    width: 34, height: 34,
    background: '#0d1117',
    borderRadius: 6,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    flexShrink: 0,
  }}>
    <svg width="28" height="28" viewBox="0 0 140 140" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <linearGradient id="ceramic3d" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0%" stopColor="#e8eef5" />
          <stop offset="50%" stopColor="#c8d8e8" />
          <stop offset="100%" stopColor="#8aaac8" />
        </linearGradient>
        <radialGradient id="ledCore" cx="40%" cy="35%" r="60%">
          <stop offset="0%" stopColor="#7AB8F5" />
          <stop offset="100%" stopColor="#2B6CB0" />
        </radialGradient>
      </defs>
      {/* 5 主臂（白瓷 ceramic3d）*/}
      <line x1="70" y1="70" x2="70"  y2="18"  stroke="url(#ceramic3d)" strokeWidth="8" strokeLinecap="round"/>
      <line x1="70" y1="70" x2="120" y2="95"  stroke="url(#ceramic3d)" strokeWidth="8" strokeLinecap="round"/>
      <line x1="70" y1="70" x2="99"  y2="128" stroke="url(#ceramic3d)" strokeWidth="8" strokeLinecap="round"/>
      <line x1="70" y1="70" x2="41"  y2="128" stroke="url(#ceramic3d)" strokeWidth="8" strokeLinecap="round"/>
      <line x1="70" y1="70" x2="20"  y2="95"  stroke="url(#ceramic3d)" strokeWidth="8" strokeLinecap="round"/>
      {/* 爪尖 */}
      <circle cx="70"  cy="18"  r="5" fill="#c8d8e8" />
      <circle cx="120" cy="95"  r="5" fill="#c8d8e8" />
      <circle cx="99"  cy="128" r="5" fill="#c8d8e8" />
      <circle cx="41"  cy="128" r="5" fill="#c8d8e8" />
      <circle cx="20"  cy="95"  r="5" fill="#c8d8e8" />
      {/* 旋轉虛線軌道 */}
      <circle cx="70" cy="70" r="42" fill="none" stroke="#4A90D9"
        strokeWidth="1" strokeDasharray="6 6" opacity="0.5"
        style={{ animation: 'ring-spin 8s linear infinite', transformOrigin: '70px 70px' }}
      />
      {/* LED 核心脈動 */}
      <circle cx="70" cy="70" r="16" fill="url(#ledCore)"
        style={{ animation: 'orb-pulse 1.5s ease-in-out infinite', transformOrigin: '70px 70px' }}
      />
      <circle cx="65" cy="65" r="5" fill="white" opacity="0.4" />
    </svg>
  </div>
  <div>
    <div style={{ fontSize: 13, fontWeight: 800, color: '#141413', letterSpacing: '2px', fontFamily: 'monospace' }}>
      AWOOOI
    </div>
    <div style={{ fontSize: 8, color: '#87867f', letterSpacing: '1px' }}>
      {tDashboard('title')} · AI OPERATIONS
    </div>
  </div>
</div>
```

- [ ] **Step 3: 確認 tDashboard('title') 映射正確**

`tDashboard('title')` 在 Task 6 已改為 `AI中心` / `AI Center`，確認 header 的 `useTranslations('dashboard')` 存在。

- [ ] **Step 4: Confirm TypeScript 無錯誤**

```bash
cd apps/web && npx tsc --noEmit 2>&1 | grep header
```

Expected: 無輸出

- [ ] **Step 5: Commit**

```bash
git add apps/web/src/components/layout/header.tsx
git commit -m "feat(header): update NemoClaw Logo SVG with ceramic3d arms and LED core"
```

---

## Task 12: AppLayout 整合確認（Sidebar 寬度固定 200px）

**Files:**
- Read: `apps/web/src/components/layout/app-layout.tsx` (或相關 layout wrapper)

- [ ] **Step 1: 確認 AppLayout 的 sidebar 寬度**

```bash
grep -n "sidebar\|200\|sidebarWidth\|w-\[200" apps/web/src/components/layout/app-layout.tsx 2>/dev/null || \
grep -rn "AppLayout\|sidebar.*width\|sidebarWidth" apps/web/src/components/layout/ | head -20
```

- [ ] **Step 2: 若 AppLayout 包含主頁佈局 wrapper，確認不衝突**

Task 10 的 `page.tsx` 使用 `<AppLayout locale={locale}>` 包裝。確認 AppLayout 不會額外加 padding/margin 影響 Feed/RightPanel 的 flex:1 比例。

若 AppLayout 有固定 max-width 或 padding，在 page.tsx 的 JSX 根元素加 `style={{ margin: 0, padding: 0, maxWidth: 'none' }}` override。

- [ ] **Step 3: Commit（若有修改）**

```bash
git add apps/web/src/components/layout/app-layout.tsx
git commit -m "fix(layout): ensure AppLayout does not constrain AI Center 3-col layout"
```

---

## Task 13: E2E 確認（本地啟動驗收）

- [ ] **Step 1: 啟動開發伺服器**

```bash
cd apps/web && npm run dev
```

Expected: 在 `http://localhost:3000` 啟動，無 TypeScript/Build 錯誤

- [ ] **Step 2: 對照 mockup 檢查清單**

瀏覽 `http://localhost:3000`，對照 `.superpowers/brainstorm/31359-1775037143/content/final-v6.html`：

```
□ Header：NemoClaw Logo（白瓷臂 + 藍色 LED） + AWOOOI + AI中心 · AI OPERATIONS
□ Metrics Strip：6 個指標正確顯示（無數據顯示 "--"）
□ Sidebar：4分區菜單，AI中心 active 狀態（橘紅右邊框）
□ Feed：IncidentCard 有頂邊條 + FlowPipeline + Q版龍蝦
□ Right Panel：NemoNodeAnimation（白底圓形 + 藍球脈動 + 旋轉軌道）
□ Right Panel：HostGrid 2×2 格（無數據顯示 "--"）
□ 圓角：卡片8px / 標籤10px / 按鈕20px / Banner圓角
□ 底色：米色 #f5f4ed / 暖白 #faf9f3 / 橘紅 accent #d97757
```

- [ ] **Step 3: 確認 i18n 切換正確**

切換到 `/en` 路徑，Header 顯示 `AI Center`，`/zh-TW` 顯示 `AI中心`

- [ ] **Step 4: Final commit**

```bash
git add -A
git commit -m "feat(ai-center): complete AI Center v6 redesign - Anthropic Warmth + FlowPipeline + NemoClaw"
```

- [ ] **Step 5: 推送到 Gitea**

```bash
git push gitea main
```

Expected: CD pipeline 觸發，API: ✅ Web: ✅

---

## 注意事項

1. **前端內網 IP 禁令**：`NEXT_PUBLIC_*` 不得含 `192.168.x.x`（`feedback_docker_nextjs_api_url.md`）
2. **i18n 零硬編碼**：所有 UI 文字必須走 `useTranslations()`（`feedback_i18n_zero_hardcode.md`）
3. **mock 資料禁止**：無數據顯示 `"--"`，絕不 hardcode 假值（`feedback_no_fake_data.md`）
4. **Z_INDEX**：sidebar/header 的 z-index 使用現有 `Z_INDEX` 常量，不 magic number
5. **授權邏輯**：`handleApprove`/`handleReject` CSRF token 由 `useCSRF()` hook 取得，勿 hardcode
6. **積木化**：新增元件前問 5 題（`feedback_lewooogo_modular_enforcement.md`）
