# Next Session Pickup — INFRA-F42 图标管线 diff 化（消除「全删再重建」）

> 生成: 2026-06-09 session 收尾。本任务是 Design + Infrastructure 类（先设计后实现）。
> 起手第一句: `按 docs/internal/_plans/next-session-pickup-2026-06-09-infra-f42-icon-diff-sync.md 做 INFRA-F42`

---

## 0. 起手必读（mandatory onboarding）

6-step onboarding（FIGMA_AS_SOURCE_OF_TRUTH → STATUS → PROJECT_GOAL → tracker → 本 pickup → AGENTS §0-10）+ `git log --oneline -8` + `git status`。
读 backlog 的 **INFRA-F42** entry（完整 read）。

## 1. 问题（实证根因）

`pnpm sync:figma-library --with-extract` 的图标导出步骤是**「全删再重建」**，非 diff：

[`figma-sync/export-icons.mjs:90`](../../../figma-sync/export-icons.mjs#L90)：
```js
rmSync(OUTPUT_DIR, { recursive: true, force: true })   // 先把整个 published/icons 删光
// 再分批从 Figma 网络下载 ~640 个 SVG（getImages → fetchSvg → writeFileSync），逐个写
```

**后果**（2026-06-09 实证）：sync 被 Ctrl-C 中断时，目录已清空、只补回一部分 → 104 个 `published/icons/*` 处于删除态、vitest 挂 2 个图标测试。既慢（~640 次网络 fetch）又脆（中断=数据丢失）。

**owner 期望**：先比对哪些图标变了，**只更新变了的部分**，而不是全删再重建。

> 已评估的最小稳健解 **A（原子替换：导出到 tmp 目录、全部成功后 swap）owner 明确不做**；本任务做 **B（真 diff）**。

## 2. 目标（B — 真 diff）

改 `export-icons.mjs`（及必要的配套），使图标导出：
1. **只重新获取/写入 Figma 端内容变了的图标**（避免全量 640 次下载 + 写盘）。
2. **删除 orphan**（candidate 列表里已不存在的图标，如重命名/删除）——但只在导出成功后做，不在开头 `rmSync` 全删。
3. **中断安全**：任何时刻中断都不会留下空目录 / 丢失未变的图标。

## 3. 关键设计问题（先 brainstorm 拍板，别直接写码）

**怎么判断「图标变了」？** 这是 B 的核心难点：
- `getImages(ids,'svg')` 只返回 SVG 的 URL，`fetchSvg` 才下载内容。**要知道内容变没变，理论上仍得 fetch**（除非有 Figma 端 version 信号）。
- 候选数据源：`figma-sync/lib/affordance-sot.mjs` / `affordance-categories/`（SoT，含 component_key + nodeId）。查里面有没有可用的 version / lastModified / hash 字段。
- 可能的策略（择一或组合，brainstorm 后定）：
  - **(a) 内容 hash 比对**：仍 fetch 每个 SVG，但与磁盘现有文件 hash 比对，只在不同时 writeFileSync（省写盘 + 省下游 catalog/dist 重建，但**不省网络**）。最简单、必正确。
  - **(b) Figma version 信号**：若 getFile / node 元数据带 `lastModified` 或版本，按版本跳过未变节点的 fetch（**省网络**，但要确认 Figma API 在 Professional plan 给不给这个信号——注意 variables REST 在本 plan 403，images/nodes 不受影响）。
  - **(c) 混合**：先按 (b) 粗筛能跳过的，剩下 fetch 后按 (a) 精确比对。
- **写盘必须幂等**（沿用 INFRA-F26/F30：strip 时间戳，避免无谓 diff）。
- orphan 清理：导出成功后，对比磁盘 svg/ 文件集 vs 新 records 集，删除多出来的（定向删除，非全删）。

**先用 brainstorming skill 把 (a)/(b)/(c) 的可行性 + ROI 评估清楚（尤其 (b) 取决于 Figma API 在 Professional plan 给不给 version），给 owner 拍板，再实现。**

## 4. 约束 / 纪律

- **FIGMA_AS_SOURCE_OF_TRUTH**：图标内容真源是 Figma；diff 逻辑不能反推/伪造，比对必须基于真实 fetch 或真实 version 信号。
- **不破坏现有产物结构**：`published/icons/{svg,esm,index.json,manifest.json}` 的布局、`transformSvgCurrentColor`（单色图标 currentColor 化）、catalog 生成（`generate:icons-catalog`）下游不变。注意 `esm/` 由 `build-icon-dist.mjs` 建（与 export-icons 分离），`rmSync(OUTPUT_DIR)` 现在会连 esm 一起删——B 改完要确认 esm 不被误删。
- **gate**：`pnpm test`（vitest，含 `IconCatalogBuild` / `IconPage` 图标测试必须绿）+ `vue-tsc` + `audit:figma-vs-sot`（图标 SoT 对账）+ `pnpm sync:figma-library --with-extract` 端到端实跑一次（验证 diff 行为 + 中断安全，可手动 Ctrl-C 测中断后目录不空）。
- **owner 直 commit master**；commit 默认含 push；改 `figma-sync/*` 是 infra，plan owner 可 direct（path B），但**先 brainstorm 设计、拍板再写**。
- 图标导出是真实 Figma 网络操作，跑得慢，留足时间。

## 5. 完成定义

- export-icons 改为 diff 化（只更新变了的图标 + orphan 定向清理 + 中断安全）。
- 端到端 `sync:figma-library --with-extract` 实跑：图标该更新的更新、未变的不动、无空目录窗口。
- 全 gate 绿；backlog INFRA-F42 标 shipped；tracker 追加一行。
