---
title: 非颜色 token 是否纳入 Figma 变量化 sync+gate —— 评估
date: 2026-07-10
backlog: INFRA-F57
status: draft
---

# 非颜色 token（Effect/Text Style + 间距/尺寸）Figma 变量化 sync+gate 评估

> owner 决策就绪评估。只读分析，未改动任何源文件。证据来自直接读取 `src/tokens/variables.css`、
> `figma-sync/extract.mjs`、`figma-sync/generate-tokens.mjs`、`scripts/audit-token-contract.mjs`、
> `figma-data/normalized/figma-styles.json`、`figma-data/render-verification-manifest.json`、
> `.github/workflows/publish.yml`。

---

## 1. 非颜色 token 现状清点

`src/tokens/variables.css` 共 453 行，声明式 token 约 293 条，其中 **195 条颜色相关**（54 个 Figma
Variable 原始色板 + 语义色层，双主题各一份 + 组件色）、**约 98 条非颜色**。非颜色分四类：

| 类别 | 代表 token | 行号 | 条数(约) | 当前"对齐"方式 |
|---|---|---|---|---|
| 间距 scale | `--sp-xxs`…`--sp-xxxl`（8）+ `--r-xs`…`--r-xxl`（6） | L120-137 | 14 | 手写值 + 注释；无 script 校验 |
| 控件尺寸 | `--control-height-xxs`、`--checkbox-size`、`--radio-circle-size`、`--slider-thumb-size-*`、`--switch-track-*`、`--form-item-*`、`--pagination-*`、`--topbar-*` 等 | L202-252 | ~40 | 手写值，部分行内注释"figma 真源" |
| Effect Style | `--shadow-l1/l2/l3`（3-layer box-shadow 组合）+ `--mask-overlay*` | L259-274 | 5 | 手写 CSS，注释指向 `figma-data/normalized/figma-styles.json effectStyles` |
| Text Style | `--text-style-*`（7 组合）+ 原子 `--font-size-*`/`--line-height-*`/`--font-weight-*`（16） | L281-305 | 23 | 手写，注释同上指向 `figma-styles.json textStyles` |
| 组件变体尺寸/颜色混合 | `--notification-*`、`--progress-*`、`--slider-*`、`--switch-*`、`--tooltip-*`、`--badge-*` 等 | L177-252 散布 | ~16 | 手写值 + 零散注释（如 "visual fix (CANONICAL-004); figma sub-node 真源 pending T1c"） |

**关键代码证据**（`figma-sync/generate-tokens.mjs` L21-23、`scripts/audit-token-contract.mjs` L16-18）：两个脚本都明确
声明非颜色 token "PRESERVED verbatim...NOT gated"——即 `pnpm generate` 不刷新它们，`audit:token-contract`
也不校验它们。这是设计好的行为，不是遗漏。

### Silent-drift 风险实测——比 backlog 描述更细，**不是铁板一块**

读 `figma-data/render-verification-manifest.json`（930 entries，实际枚举所有字段）发现它已包含
`rootWidth/rootHeight/rootPadding/rootRadius/rootGap/rootOpacity/rootBorderWidth`——这些数值直接从 Figma
节点几何抽取（非 Variable），并通过 `pnpm test:render-verification`（Playwright `getComputedStyle()`）+
`pnpm audit:render-drift-gate`（`BASELINE_A=0`）比对，且**已在 `.github/workflows/publish.yml` L71-80
release-time CI 硬闸**（tag push 触发，非每次 PR）。

→ **间距/尺寸在"组件消费层"其实已有确定性 gate**——如果设计师改了某组件的 padding/gap/radius/宽高，
release 前会被 render-drift-gate 抓到。真正没被覆盖的是**抽象 scale 本身**（8 个 `--sp-*` + 6 个
`--r-*` 数值定义）：只要每个消费组件的实际数值仍与 Figma 节点吻合，scale 命名/分组漂移不会被抓到——
这是更窄的残余风险，不等于 backlog 原文暗示的"间距完全 silent-drift"。

Effect Style / Text Style **没有任何等价 runtime gate**（枚举 manifest 全部字段，没有 `fontSize` /
`lineHeight` / `fontWeight` / `boxShadow` 相关 key）——这部分风险是真的、且当前 100% 未覆盖。

**但材料已经在**：`figma-sync/extract.mjs` 的 `extractStyles()`（L597-779）每次 `pnpm sync:figma-library`
都会调用 Figma `getFileStyles()` + `getNodes()` **实时**重抓 TEXT/EFFECT 两类 Style（L644-721），写入
`figma-data/normalized/figma-styles.json`，且该文件**已经**手工标注好 `canonicalToken` 字段——直接映射到
CSS 变量名（如 `'L2 shadow' → canonicalToken: '--shadow-l2'`、`'Roboto/22 large title' →
'--text-style-large-title-22'`，实测 14 条 textStyles + 4 条 effectStyles 全部已标注，
`_meta.extractedAt: 2026-05-18`，且每次 sync 会刷新数值部分）。

**间距/尺寸 scale（`scaleTokens` 字段）完全不同**：`extract.mjs` L774 `if (existing.scaleTokens) output.scaleTokens
= existing.scaleTokens` —— 无条件原样搬运，从未被任何 Figma API 调用刷新过。它是 2026-05-18 一次性手打的
文档（`figmaPath: "tvu design system.spacing.xxs → basic size.#4"`，指向某个 Figma 帧/文本标注，不是
Variable 也不是 Style），此后再也没有活水源。

### 结论清点

| 子类 | 组件消费层是否已有 gate | Style/Scale 层是否有实时数据源 | 净风险 |
|---|---|---|---|
| 间距/尺寸（组件消费） | ✅ 有（render-verification-manifest, CI release-gate） | — | 低（已覆盖） |
| 间距/尺寸（抽象 scale 本身） | ❌ 无 | ❌ 无（scaleTokens 从未活刷新） | 中——但没人在改这 8/6 个数字，历史零漂移事故 |
| Effect Style（`--shadow-*`） | ❌ 无 | ✅ 有（`figma-styles.json effectStyles`，每次 sync 刷新） | 高但**低成本可关闭** |
| Text Style（`--text-style-*` + 原子） | ❌ 无 | ✅ 有（`figma-styles.json textStyles`，每次 sync 刷新） | 高但**低成本可关闭** |

---

## 2. Figma 侧：Variable 还是 Style？技术前提

- **54 个 Figma Variable 实测全是颜色**（`figma-data/normalized/variables.json`，逐条枚举 `figmaName`：
  `UX/Grey/*`、`UX/Brand/*`、`UX/Red|Orange|Blue/*`、`Color Type/Text|Icon|Background|Line/*`——零 FLOAT/间距条目）。
- **Effect Style / Text Style 在 Figma 数据模型里是"Style"对象**（`getFileStyles()` 返回
  `style_type: TEXT/EFFECT/FILL/GRID`），与"Variable"（Figma 平台原生只支持 Color/Number/String/Boolean 四种
  scalar 类型）是两套不同机制。Style 是"具名可复用的属性组合"（一整套 font-size+line-height+weight，或一整套
  多层 drop-shadow），Figma 并不支持把一个"Text Style"或"Effect Style"整体绑定为单个 Variable——要变量化，
  必须把组合拆成独立标量（每层 shadow 的 offset-x/y/blur/spread/color、text 的 font-size/line-height 各自
  绑一个 Number/Color Variable 到每个消费图层），这是**放弃"Style 复用"UX、对库里每个用到该 Style 的图层
  重新逐属性绑定**的重构，不是"建几个 Variable"的小活。（此为 Figma 平台既有产品设计事实，非本仓库特有限制；
  建议真正推进前用 Figma MCP `get_variable_defs`/`use_figma` 现场核实一次当前 plan 下 Number Variable 绑定
  到 Effect 图层属性的可达性，本仓库已两次撞过"以为可达实际 401/需 Org·Ent seat"的坑——Code Connect 即是
  一例，`docs/PROJECT_GOAL.md` L113。)
- **间距/尺寸数值本质是标量 px 数字**——Figma Auto Layout 的 padding/gap、图层的 corner-radius、
  stroke-weight、opacity 早就支持绑定 Number Variable，这是成熟功能，不涉及"Style vs Variable"结构性障碍。
  → **间距/尺寸变量化的技术前提比 Effect/Text Style 好得多**，两者不该被 backlog 原文合并成同一个"非颜色
  token"桶一起评估——技术可行性天差地别。

---

## 3. 三选项成本/收益/风险

### ① Figma 建对应 Variable（真源方向变双写）

- **间距/尺寸**：技术可行（Number Variable + Auto Layout 属性绑定），但要求：(a) 设计师把库内每个消费节点的
  padding/gap/radius 从字面量改绑到 Variable（一次性人工工作量，规模未知，需设计师现场盘点）；(b)
  `generate-tokens.mjs`/`audit:token-contract` 要扩展成同时吃两个 primitive 源（复用 `loadPrimitives()`
  模式，工程量中等但有先例可循）；(c) 触发"真源方向变双写"——当前非颜色值是 code-authored-first
  （类比 `divergences-decisions.json` 里 code-first 拍板案例），改 Figma-first 是工作流方向变化，需要
  owner/设计师 ack，不是纯技术切换。
- **Effect/Text Style**：如 §2 所述，**结构性不可直接变量化**——要做等价于放弃 Style 抽象、逐图层逐属性重
  绑定，成本远高于"建几个 Variable"，且收益存疑（失去 Style 复用的设计工作流优势）。
- **成本：高**（Figma 侧重做 + 生成器/gate 扩展 + 现有绑定重新核验）。**收益：与颜色层完全对等的双向真源模型
  ——但只对间距/尺寸成立，对 Effect/Text 基本不可达**。**风险：中**（工作流打断；对 Effect/Text 很可能白做）。

### ② 保持 Style + 加"Style→code 值对齐"确定性 audit（不建 Variable）

- **Effect/Text Style：近乎免费**。`extractStyles()` 已经每次 sync 实时重抓、`figma-styles.json` 已经手工
  标好 `canonicalToken` 精确映射到 CSS 变量名——只需新写一个和 `scripts/audit-token-contract.mjs`
  （52 行）同构的脚本：解析 `variables.css` 里 `--shadow-*`/`--text-style-*`/`--font-size-*`/
  `--line-height-*`/`--font-weight-*` 的当前值，对照 `figma-styles.json` 的 `cssBoxShadow`/`fontSize`/
  `lineHeight`/`fontWeight` 数值 diff，不吻合就 exit 1。**无需启发式**（`canonicalToken` 是显式标注，符合
  `FIGMA_AS_SOURCE_OF_TRUTH.md` "禁止启发式匹配"的红线）。估算：小型工作量（几小时级），复用现成模式。
- **间距/尺寸抽象 scale**：不是免费的——`scaleTokens` 从未被真实 Figma API 刷新过（§1 已证），要对它做
  audit，第一步得先立项搞清楚"这 8+6 个数字在 Figma 里到底挂在哪个可稳定查询的节点/frame"，这是一次新的
  抓取路径设计工作，不是"照抄 Effect/Text 的做法"就能复用。
  同时，如 §1 所述，**组件消费层的间距/尺寸已经被 render-verification-manifest 实质性 gate 住**——抽象
  scale 单独漂移而所有消费组件仍与 Figma 吻合，是一个更窄、优先级更低的残余场景。
- **成本：Effect/Text 低；间距 scale 未知/中高（需先立项）。收益：直接关闭当前唯一"零覆盖"的真实缺口
  （Effect/Text）；间距上是锦上添花（组件层已覆盖大头）。风险：低**——纯增量脚本，不改变现有同步方向，
  不影响其它并行 session 在动的 vite/mockup 文件。

### ③ 维持现状（accept 手工对齐）

- **成本：零**。**收益：无**（现状延续）。**风险**：Effect/Text Style 的真实、当前 100% 未覆盖的敞口继续
  存在且无收口计划；间距风险其实比 backlog 原文暗示的更可控（组件层已 gate），但抽象 scale 文档本身仍可能
  悄悄腐化（无人验证 `scaleTokens` 跟 Figma 当前真实值是否还对得上——已知从 2026-05-18 起从未刷新过）。

---

## 4. 推荐

**分层推荐，不是三选项里选一个整体套用到全部非颜色 token**——因为 Effect/Text Style 和 间距/尺寸的技术可行性
证据上截然不同：

1. **Effect Style + Text Style → 选 ②**：新写一个"Style→code 值对齐" audit script。理由：`extractStyles()`
   已经实时重抓、`canonicalToken` 映射已经手工标好，成本近乎免费；同时选①在这两类上结构性接近不可行（Style
   ≠ Variable，需放弃 Style 复用重做逐图层绑定），选③放着唯一真实缺口不管。②是这两类唯一同时满足"低成本 +
   真实关闭缺口 + 不需要 Figma 侧重构"的选项。
2. **间距/尺寸（组件消费层）→ 选 ③（现状已足够）**：930-entry render-verification-manifest + CI release-gate
   已经在做等价的事，继续维持现状即可，不需要额外投入。
3. **间距/尺寸（抽象 scale 本身）→ 选 ③ 短期，① 留作长期/待触发**：选①（真变量化）是教科书级终态，但是一次
   跨 Figma 节点绑定的真实迁移项目，成本未知、需设计师配合，不该顺手做；建议等支柱①（TS/JSON token 导出）
   或下次做"完整原子真源"专项时再评估启动，而不是现在动手。若 owner 想要一个"比现状更强但比①便宜"的中间态，
   下一步应是先立项"scaleTokens 抓取路径设计"（是否有稳定可查询的 Figma 节点），这本身是新的 investigation，
   不是"复制 Effect/Text 的现成脚本"能解决的。

### 推荐自审（4 问）

- **理由是否经得起反驳（"依赖/前置"是真是编）**：是真的——直接读了 `extract.mjs` L644-721（TEXT/EFFECT 每次
  sync 实时重抓）vs L774（scaleTokens 无条件原样搬运，从未刷新），以及 `render-verification-manifest.json`
  的字段枚举（有几何字段、无字体/阴影字段），均为直接读码/读数据验证，非推断。
- **是否把最省事项包装成 #1**：没有把"③全维持现状"包装成推荐——对 Effect/Text 明确选②（要写新脚本，非
  零成本），且指出①对这两类结构性接近不可行是产品模型事实、非"为了推荐②而贬低①"。
  对间距抽象 scale 确实推荐③/暂缓①，但同时给出了"更稳健选项①"未来触发点，没有把它压没。
- **有无更稳健选项被压脚注**：①（真变量化）在 Effect/Text 上被判定接近不可行，是给了具体产品模型理由
  （Style 组合 vs 标量 Variable），而不是简单说"太贵不做"；对间距，①作为长期终态被显式保留、给了触发条件
  （支柱①/专项时机），未被降级成脚注消失。
- **该我拍还是 owner 拍**：这是 backlog 明确标注的"owner 拍"架构决策；本报告只提供证据 + 推荐，不代为决策、
  未改动任何源文件。

---

## 附：关键文件/行号索引

| 证据点 | 文件:行 |
|---|---|
| 非颜色 token 不被 gate 的设计说明 | `figma-sync/generate-tokens.mjs:21-23`、`scripts/audit-token-contract.mjs:16-18` |
| 间距/尺寸组件消费层实测已被 render-verification 覆盖 | `figma-data/render-verification-manifest.json`（930 entries，字段含 rootPadding/rootGap/rootRadius/rootWidth/rootHeight） |
| render-verification CI release-gate 硬闸 | `.github/workflows/publish.yml:71-80`、`scripts/audit-render-drift-gate.mjs:1-26`（`BASELINE_A=0`） |
| Effect/Text Style 每次 sync 实时重抓 | `figma-sync/extract.mjs:597-721`（`extractStyles()`） |
| Effect/Text 已有 `canonicalToken` 精确映射 | `figma-data/normalized/figma-styles.json`（`textStyles`/`effectStyles`，14+4 条） |
| 间距 scale 从未被 Figma API 刷新（原样搬运） | `figma-sync/extract.mjs:774` |
| 54 个 Figma Variable 全是颜色 | `figma-data/normalized/variables.json`（逐条 `figmaName` 枚举） |
| 非颜色 token 现状清点行号 | `src/tokens/variables.css:120-137`（间距/圆角）、`:177-252`（控件/组件变体尺寸）、`:259-274`（Effect）、`:281-305`（Text Style） |
