# 视觉 diff 工作流（pangolin 客户端 ↔ design/ui_kits 原型）

用 `design-distill` skill 的截图 diff 工具，把 Flutter 实现与 `design/ui_kits/{desktop,tablet,mobile}` React 原型对照。

## 工具
- 原型截图：`~/.claude/skills/design-distill/tools/shoot-prototype.mjs`
- 像素 diff：`~/.claude/skills/design-distill/tools/diff.mjs`

## 原型基准（已验证可用）
从项目根运行（脚本自动起临时 http server，让 Babel 原型能 fetch jsx）：

```bash
SHOOT=~/.claude/skills/design-distill/tools/shoot-prototype.mjs
# desktop 连接页 明/暗（920×600 内容，截外层 sand 画布给点余量）
node $SHOOT design/ui_kits/desktop/index.html shots/desktop_connect_light.png \
  --width 1000 --height 720 --theme light --lang zh --wait-for "#root > *"
node $SHOOT design/ui_kits/desktop/index.html shots/desktop_connect_dark.png \
  --width 1000 --height 720 --theme dark  --lang zh --wait-for "#root > *"
```
> 原型是 SPA，默认显示连接页。截其它页需给 shoot-prototype 加 `--click <navSelector>`（待补）。

## diff
```bash
node ~/.claude/skills/design-distill/tools/diff.mjs <baseline.png> <impl.png> <diff.png> --threshold 0.05
```

## 实现侧截图：两条路径与现状

### A. Flutter golden（已启用 ✅，CI 严格回归闸）
`flutter test --update-goldens test/golden` 截 `RepaintBoundary`，无 chrome/窗口问题，入 CI。
**字体方案**（已落地）：`PangolinFonts.useBundled` 开关 + `test/flutter_test_config.dart` 用 `FontLoader`
注册 `test/fonts/` 真实 Sora/Manrope/JetBrainsMono + Lucide 图标字体，测试态绕过 google_fonts。
- 改组件视觉后：`flutter test test/golden` 失败即回归；确认是预期变更则 `--update-goldens` 重生成基准。
- 现覆盖：连接键三态 + 推荐卡 + 额度卡 × 明暗（`test/golden/components_golden_test.dart`）。
- 注：CJK 文案用 fallback（Noto Sans SC 未打包，体积大），基准自洽不影响回归检测；新增屏/组件按此模式扩展。

### B. screencapture 真实 app（已可截，但受限）
真实渲染、字体真实，但：macOS 原生标题栏(红绿灯) vs 原型自绘 chrome 结构不同；窗口精确定位需「辅助功能」权限。
**用途**：作为**人工视觉对照**（生成 diff 图定位结构偏差），非严格像素闸。

### C. iOS 模拟器（最干净的全屏，未接）
`xcrun simctl io booted screenshot` 截模拟器无桌面 chrome、字体真实，适合 mobile/tablet。导航多页仍需 integration_test 驱动。

## 建议
- **严格回归闸** → 走 A，先做 `PangolinText` 字体可注入（一次性小重构），启用 golden。
- **对照设计原型** → 原型基准（本文档）+ A 的 golden 截图，用 diff.mjs 比对。
- desktop 因 chrome 差异，像素级 100% 对齐不是目标；以**布局/间距/颜色结构一致**为验收口径。
