visionA/docs/autoflow/03-design/flows/flow-pairing.md
jim800121chen fb7da5d180 chore(autoflow): migrate .autoflow/ 共享層文件至 docs/autoflow/
依 autoflow-agent workspace v2 設計把 PRD / 設計 / 架構 / 交付類
共享文件從個人層 .autoflow/(ignored)搬到 docs/autoflow/(進 git),
讓團隊可共享產品與架構文件,個人層只留 progress / review / testing 等
per-branch 筆記。

- 02-prd/        21 個檔(PRD、features、market-analysis 等)
- 03-design/     18 個檔(design-spec、wireframes、flows 等)
- 04-architecture/ 31 個檔(TDD、design-doc、ADR×14、API 規格等)
- 07-delivery/   3 個檔(project-summary、phase-0.6-handover、stage-deployment-setup)

合計 73 檔。原檔已從 .autoflow/ 移除(migration 工具執行 git mv,
但因 .autoflow/ 在 .gitignore 中、git 將此操作視為新增、無 rename history)。
2026-05-04 16:55:55 +08:00

505 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pairing 流程設計 — visionA Cloud
> **雲端版最關鍵的新增流程**。使用者要能在雲端 Web 取得 Pairing Token、帶到自己電腦上的 local agent、完成配對之後就能從瀏覽器遠端控制自己的 Kneron 裝置。
>
> 配套的文字版 wireframe 見 [`wireframes/wf-pairing.md`](../wireframes/wf-pairing.md)。
---
## 1. User Story
> **作為** 一個 Kneron 開發者,
> **我想要** 用一組短短的 token 讓雲端和我桌機上的 Kneron 裝置建立連線,
> **這樣** 我就能從任何地方(出差、客戶端、家裡)用瀏覽器操作我的裝置,不需要每次都連 VPN 或 SSH。
**成功條件**
- Token 產生到完成連線 < 3 分鐘
- 使用者不需要懂 tunnel / yamux / WebSocket 等技術細節
- 失敗時能明確告訴使用者哪一步錯怎麼修
---
## 2. 流程全景圖
```
雲端 Web 使用者電腦 雲端後端
─────── ───────── ────────
local agent
(Go binary)
[登入] ──→ [/devices/pair] ──────────────────────────────────→ POST /api/pairing/token
▼ ↓
產生 token 產生 vAc_ + 32 hex token
▼ (綁 user_id, TTL 15 分鐘)
[PairingTokenCard 顯示 token] ↓
▼ 回傳 token
使用者按「複製」 ←─────
切換到「下載 local agent」分頁
根據 OS 顯示對應下載連結 + 指令
使用者下載、安裝、啟動 local agent
▼ local agent 啟動 ────→ GET /tunnel/connect?token=xxx
▼ ↓
第 3 步:等待連線 WebSocket 升級
▼ yamux session 建立
[polling] GET /api/pairing/status?token=xxx ──────────────────→ ↓
▲ 回 200 + 裝置資訊
│ ↓
◀───────────────────────────────────────────────────────────
「✓ 已連線!」+ 顯示裝置資訊
[繼續] 按鈕
跳 /devices顯示新裝置
```
---
### 2.1 Token 格式與兩階段 Token 模型
**雲端有兩種 token使用者只會接觸到第一種**
| Token | 用途 | 格式 | TTL | 使用者可見 |
|-------|------|------|-----|----------|
| **Pairing Token** | 一次性配對 token使用者貼到 local agent 啟動連線 | `vAc_` + 32 字元 hex總長 36| **15 分鐘** | 看得到要複製 |
| **Session Token** | Local agent 換到後維持 tunnel 連線用 | 後端規格使用者不關心 | 90 | 完全看不到 |
**換取流程(使用者無感):**
```
1. 雲端 Web 產生 Pairing TokenvAc_xxxx…→ 使用者複製
2. 使用者貼到 local agent → local agent 用這個 token 打 /tunnel/connect
3. 雲端驗證 Pairing Token → 核發 Session Token 給 local agent
4. Pairing Token 立即失效one-time use
5. 後續 local agent 所有連線都帶 Session Token使用者完全感受不到
```
**UI 顯示策略36 字元太長、15 分鐘很緊):**
- API 回傳完整 `vAc_` + 32 hex一整串 36 字元無空格
- **UI 顯示切成兩行**提升可讀性`font-mono text-xl tracking-wider`
- 1 `vAc_` 前綴 + 16 字元 hex 20 字元
- 2 16 字元 hex
- 行內用 `-` 或純空白切開都行**切法純視覺**`select-all` 選取時選到整個區塊
- **實際複製到剪貼簿的永遠是無空格無換行的完整字串**`vAc_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8`
- Mobile< 640px字級降為 `text-lg`仍切兩行不允許水平捲動
- 螢幕閱讀器`aria-label` 給完整字串但文字節點用分組避免一字一念
---
## 3. 三步式 Stepper
使用既有視覺語彙 Tabs Stepper UI 呈現。**建議使用 Stepper**因為步驟有順序依賴)。
### 3.1 視覺
```
┌─────────────────────────────────────────────────────────────────┐
│ ← 返回 │
│ │
│ 配對新裝置 │
│ 讓你的 Kneron 裝置連上雲端,就能從任何地方遠端操作 │
│ │
│ ●──────────────────●──────────────────○ │
│ 1 2 3 │
│ 取得 Token 設定 Local Agent 確認連線 │
│ │
│ [當前步驟內容] │
│ │
│ [上一步] [下一步] │
└─────────────────────────────────────────────────────────────────┘
```
### 3.2 Stepper 規格
- 步驟指示器`flex items-center gap-2`使用 Lucide `Circle` / `CheckCircle`
- 已完成`bg-primary text-primary-foreground` + `CheckCircle` 填色
- 當前`ring-2 ring-primary bg-background`
- 未完成`bg-muted text-muted-foreground`
- 步驟間連線`h-0.5 bg-muted`已完成段落 `bg-primary`
- 下方步驟標題`text-sm font-medium`已完成/當前)、`text-muted-foreground`未完成
### 3.3 底部按鈕
- 上一步`variant=outline` 1 disabled
- 下一步`variant=default` 3 步改為完成 進入裝置列表
---
## 4. Step 1 — 取得 Token
### 4.1 版型
```
┌──────────────────────────────────────────────────────────────┐
│ Step 1 · 取得 Pairing Token │
│ │
│ 複製下方 token在 Step 2 讓 local agent 使用這組 token 連線雲端 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 🔗 你的 Pairing Token │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ vAc_a1b2c3d4e5f6a7b8 │ │ │
│ │ │ c9d0e1f2a3b4c5d6e7f8 │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ (視覺切兩行,複製永遠是完整 36 字元無空格) │ │
│ │ │ │
│ │ [📋 複製] [🔄 重新產生] │ │
│ │ │ │
│ │ ⏱ 剩餘 14:52 ─────────────────────── │ │
│ │ 進度條bg-primary隨時間變 amber → red │ │
│ │ 📅 產生時間14:30 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ⚠ 這組 token 15 分鐘內有效,請立刻到 Step 2 完成配對 │
│ token 是一次性使用,完成配對後自動失效 │
│ │
└──────────────────────────────────────────────────────────────┘
```
### 4.2 主要元件
- `PairingTokenCard` `components.md` 10.2)— 含倒數計時器
- 下方安全提示`Callout` 樣式`bg-amber-50 border-amber-300`或用 Alert 元件
### 4.3 初始狀態
- 進入頁面自動呼叫 `POST /api/pairing/token` 產生新 token`vAc_` + 32 hexTTL 15 分鐘
- 客戶端啟動 1 秒一次的倒數計時 `expiresAt - now()` 算起 polling 後端
- API 失敗顯示 error state 無法產生 token請重試」+ 重試按鈕
### 4.4 倒數計時器視覺規格
TTL 72 小時降到 15 分鐘倒數計時器要**顯著且突出**讓使用者意識到要馬上行動
| 剩餘時間 | 視覺 | 行為 |
|---------|------|------|
| > 10:00 | `text-foreground` + 進度條 `bg-primary` | 正常顯示 |
| 10:00 3:00 | `text-amber-600` + 進度條 `bg-amber-500` | 文字變粗 `font-medium` |
| 3:00 0:30 | `text-red-600` + 進度條 `bg-red-500` + 卡片 `ring-1 ring-red-300` | 計時文字前加 ⚠;建議配 `prefers-reduced-motion` 尊重,不閃爍 |
| ≤ 0:30 | 同上 + Toast「Token 即將過期,請立刻完成或重新產生」 | 只 Toast 一次 |
| 00:00 | Token 轉灰 `text-muted-foreground line-through`;複製按鈕 disabled顯示「此 token 已過期,請重新產生」 | `aria-live="assertive"` 宣告 |
**格式**`mm:ss`(例:`14:52``02:17``00:08`不要顯示小時15 分鐘內絕不超過 15:00。
### 4.5 互動行為
| 操作 | 行為 |
|------|------|
| 點「複製」| 呼叫 `navigator.clipboard.writeText(token)` 複製**完整 36 字元無空格**字串;按鈕暫態變「已複製 ✓」2 秒toast「Token 已複製到剪貼簿15 分鐘內有效」|
| 點「重新產生」| 開 AlertDialog「確定要重新產生舊 token 將立即失效,新 token 有效期 15 分鐘。」→ 確認後 POST API更新 UI重啟倒數 |
| Token 過期0:00| 自動將 UI 切至過期狀態;主 CTA 從「下一步」改為「重新產生 token」|
| 進入 Step 2 前 | 檢查使用者是否已複製(用 state。未複製則禁止下一步提示「請先複製 token」或允許繼續但 toast warning |
### 4.6 i18n key
```
pairing.step1.title → Step 1 · 取得 Pairing Token
pairing.step1.description → 複製下方 token15 分鐘內到 Step 2 完成配對
pairing.token.title → 你的 Pairing Token
pairing.copy → 複製
pairing.copied → 已複製
pairing.regenerate → 重新產生
pairing.timeRemaining → 剩餘 {time}
pairing.expiresIn15min → 15 分鐘內有效
pairing.generatedAt → 產生時間:{time}
pairing.security.warning → 這組 token 15 分鐘內有效,請立刻到 Step 2 完成配對
pairing.security.oneTime → token 是一次性使用,完成配對後自動失效
pairing.regenerateConfirm.title → 確定要重新產生?
pairing.regenerateConfirm.description → 舊 token 將立即失效,新 token 有效期 15 分鐘
pairing.toast.copied → Token 已複製到剪貼簿15 分鐘內有效
pairing.toast.generateFailed → 無法產生 token請重試
pairing.toast.expiringSoon → Token 即將過期,請立刻完成或重新產生
pairing.token.expired.label → 此 token 已過期,請重新產生
```
---
## 5. Step 2 — 設定 Local Agent
### 5.1 版型
```
┌──────────────────────────────────────────────────────────────┐
│ Step 2 · 設定 Local Agent │
│ │
│ 在你的電腦下載並啟動 local agent讓它連上雲端 │
│ │
│ 選擇作業系統: │
│ [ macOS ] [ Windows ] [ Linux ] │
│ ▲ active │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. 下載 local agent │ │
│ │ [📥 下載 visionA-local-agent-macos.dmg] │ │
│ │ │ │
│ │ 2. 安裝並啟動 │ │
│ │ 打開 DMG拖曳到 Applications雙擊啟動 │ │
│ │ │ │
│ │ 3. 輸入 Pairing Token │ │
│ │ 在 local agent 的「雲端」設定頁貼上 token │ │
│ │ │ │
│ │ 或使用命令列CLI 進階使用者): │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ visiona-agent --pair-token=\ │ │ │
│ │ │ vAc_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8 │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ │ (複製時為完整單行無反斜線) │ │
│ │ │ │
│ │ 4. 連線成功後,回到這個頁面進入 Step 3 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 💡 已經有 local agent[跳到 Step 3] │
└──────────────────────────────────────────────────────────────┘
```
### 5.2 作業系統分頁
使用 `Tabs` 元件,三個 tab`macOS` / `Windows` / `Linux`,自動偵測 `navigator.userAgent` 設預設。
**平台差異:**
| OS | 下載檔案 | 安裝步驟 | 命令列 |
|----|---------|---------|--------|
| macOS | `.dmg` | 拖曳到 Applications | `visiona-agent --pair-token=vAc_...` |
| Windows | `.exe` installer | 雙擊執行 | `visiona-agent.exe --pair-token=vAc_...` |
| Linux | `.AppImage` | `chmod +x` 後執行 | `./visiona-agent --pair-token=vAc_...` |
### 5.3 指令區塊
- 使用 `bg-muted font-mono text-sm p-3 rounded-md` 樣式
- 右上角有「複製」小按鈕
- 將使用者 token 自動嵌入指令(`--pair-token=[當前 token]`
### 5.4 互動
| 操作 | 行為 |
|------|------|
| 切換 OS Tab | 內容重新渲染 |
| 點「下載」按鈕 | 直接下載對應 OS 的 installer |
| 點指令「複製」| 複製整行 `visiona-agent --pair-token=vAc_xxxx…`(完整 36 字元 token單行無跳行|
| 「已經有 local agent」| 直接跳 Step 3 |
### 5.5 i18n key
```
pairing.step2.title → Step 2 · 設定 Local Agent
pairing.step2.description → 在你的電腦下載並啟動 local agent...
pairing.step2.selectOS → 選擇作業系統
pairing.step2.download → 下載 local agent
pairing.step2.install → 安裝並啟動
pairing.step2.installHint.macos → 打開 DMG拖曳到 Applications...
pairing.step2.installHint.windows → 雙擊 installer...
pairing.step2.installHint.linux → chmod +x 後執行...
pairing.step2.enterToken → 輸入 Pairing Token
pairing.step2.tokenHint → 在 local agent 的「雲端」設定頁貼上 token
pairing.step2.orCli → 或使用命令列CLI 進階使用者):
pairing.step2.waitConnect → 連線成功後,回到這個頁面進入 Step 3
pairing.step2.alreadyHaveAgent → 已經有 local agent[跳到 Step 3]
```
---
## 6. Step 3 — 確認連線
### 6.1 視覺(等待中)
```
┌──────────────────────────────────────────────────────────────┐
│ Step 3 · 確認連線 │
│ │
│ ⏳ 等待 local agent 連線... │
│ │
│ 已等待 0:15最長 3 分鐘) │
│ │
│ [取消] [查看 Troubleshooting] │
│ │
│ 提示: │
│ • 確認 local agent 已啟動 │
│ • 確認 token 正確無誤 │
│ • 確認你的網路可以連到 cloud.visiona.ai │
└──────────────────────────────────────────────────────────────┘
```
### 6.2 視覺(成功)
```
┌──────────────────────────────────────────────────────────────┐
│ Step 3 · 確認連線 │
│ │
│ ✓ 已成功連線! │
│ │
│ 檢測到的裝置: │
│ ┌────────────────────────────────────┐ │
│ │ 🔌 Kneron KL520 │ │
│ │ Firmware 2.3.1 · Host: office-mac│ │
│ └────────────────────────────────────┘ │
│ │
│ [進入裝置列表 →] │
└──────────────────────────────────────────────────────────────┘
```
### 6.3 視覺(失敗 / 超時)
```
┌──────────────────────────────────────────────────────────────┐
│ Step 3 · 確認連線 │
│ │
│ ⚠ 連線超時 │
│ │
│ 超過 3 分鐘沒收到 local agent 連線 │
│ │
│ 可能原因: │
│ • local agent 尚未啟動 │
│ • token 輸入錯誤 │
│ • 防火牆擋住 WebSocket 連線 │
│ │
│ [重新檢查] [回到 Step 2] [查看完整說明] │
└──────────────────────────────────────────────────────────────┘
```
### 6.4 行為
- 進入 Step 3 自動開始 polling `/api/pairing/status?token=xxx`(每 3 秒)
- 計時器顯示已等待時間
- 成功(`status: connected`):停止 polling顯示成功畫面 + 裝置資訊
- 超時(> 3 分鐘):停止 polling顯示失敗畫面
- 「取消」:返回 `/devices`token 保留(使用者可稍後重試)
- 「重新檢查」:重啟 polling
### 6.5 i18n key
```
pairing.step3.title → Step 3 · 確認連線
pairing.step3.waiting → 等待 local agent 連線...
pairing.step3.elapsed → 已等待 {time}(最長 3 分鐘)
pairing.step3.cancel → 取消
pairing.step3.troubleshooting → 查看 Troubleshooting
pairing.step3.hints.title → 提示:
pairing.step3.hints.running → 確認 local agent 已啟動
pairing.step3.hints.token → 確認 token 正確無誤
pairing.step3.hints.network → 確認你的網路可以連到 cloud.visiona.ai
pairing.step3.success → 已成功連線!
pairing.step3.success.detected → 檢測到的裝置:
pairing.step3.success.continue → 進入裝置列表 →
pairing.step3.failure.timeout → 連線超時
pairing.step3.failure.reason → 超過 3 分鐘沒收到 local agent 連線
pairing.step3.failure.causes → 可能原因:
pairing.step3.failure.retry → 重新檢查
pairing.step3.failure.backToStep2 → 回到 Step 2
pairing.step3.failure.docs → 查看完整說明
```
---
## 7. 邊界情境
### 7.1 Pairing Token 過期15 分鐘後)
- 進入 Step 3 polling 時若收到 `status: expired` → 顯示「Token 已過期」+ 「回到 Step 1 重新產生」
- Step 1 若使用者停留超過 15 分鐘未下一步,倒數歸零 UI 會自動切到過期狀態(見 4.4
- **注意**:這裡過期的是 Pairing Token15 分鐘。Local agent 一旦換到 Session Token90 天),後續連線不受影響,使用者看不到這層
### 7.2 同一 token 被多次使用
- Architect 端決策token 應該是**一次性**的(綁第一個連上的 local agent
- 後續若同 token 被其他 agent 嘗試連線 → Step 3 polling 顯示「此 token 已被其他裝置使用」
### 7.3 網路中斷polling 失敗)
- 顯示臨時錯誤 + 自動重試(指數退避)
- 3 次失敗後提示「網路不穩,請檢查連線」
### 7.4 頁面離開 / 重新整理
- 若 token 已產生但未完成連線:重新進入頁面應該偵測到並回到 Step 3或重回 Step 1 重新產)
- 使用 URL query param `?token=xxx` 保存進行中的 token
### 7.5 多裝置一次配對
- Phase 0一次只配對一個裝置一個 token 對一台 agent / 一台 host
- Phase 1考慮支援一台 host 多張 Kneron 卡agent 端回報多裝置)
---
## 8. 成功後的導航
完成配對後:
1.`/devices` 列表看到新裝置(`RemoteDeviceBadge` = 在線)
2. Toast「裝置 Kneron KL520 已成功配對」
3. Activity Timeline 新增一筆 `device_paired` 紀錄
---
## 9. 給 Architect / Backend 的 API 契約提議
### 9.1 `POST /api/pairing/token`
Pairing Token`vAc_` 前綴 + 32 字元 hexTTL 15 分鐘,一次性使用。
```json
Request: (需 auth雛形可 skip auth
Response 200:
{
"token": "vAc_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8",
"expiresAt": "2026-04-21T14:45:00Z",
"createdAt": "2026-04-21T14:30:00Z",
"ttlSeconds": 900
}
```
Session Token使用者看不到雲端 ↔ local agent 內部TTL 90 天Pairing Token 成功換取後發給 local agent。此層 UI 無關,僅記錄備查。
### 9.2 `GET /api/pairing/status?token=xxx`
```json
Response 200:
{
"status": "pending" | "connected" | "expired" | "used",
"device": {
"id": "kl520-usb-0001",
"name": "Kneron KL520",
"type": "KL520",
"firmwareVersion": "2.3.1",
"hostName": "office-mac"
} | null,
"pairedAt": "2026-04-21T14:35:00Z" | null
}
```
### 9.3 `DELETE /api/pairing/token/:token`
重新產生時,先刪除舊的。
### 9.4 `POST /api/devices/:id/unpair`
解除配對(從 `/devices/[id]` 的 DeviceSettingsCard 或 `/account` 觸發)。
---
## 10. 無障礙檢查
- Stepper`role="list"` + 每個 step `role="listitem"` + 當前 step `aria-current="step"`
- Token 顯示:`aria-label="Pairing token, {token}"`(但不念出字元)
- 複製按鈕:`aria-live="polite"` 宣告「已複製」
- Step 3 等待:`aria-live="polite"` 宣告 status 變更
- 所有按鈕可 Tab 聚焦、Enter / Space 觸發
- 錯誤訊息:`role="alert"`
---
## 11. TODO
| 項目 | 時機 |
|------|------|
| Local agent UI 的「雲端」分頁設計(接收 token 的那端)| Phase 0 同步設計(需跨 local-tool另開需求|
| ~~Token 過期警告~~ → 已納入 Phase 0見 4.4 倒數計時器,因 TTL 降至 15 分鐘必做)| — |
| 支援 Email 發送 token方便跨裝置| Phase 2 |
| QR Code 配對(手機掃描)| Phase 2 |
| 一次性 pairing code更短 6 位數字UX 更好)| Phase 2 |