依据描述生成 DIM,再依据 DIM 同步到 Figma 的完整工程流程
这篇文档把DIM到Figma的能力串成一条完整链路:先把自然语言描述沉淀成结构化的 DIM,再把 DIM 作为唯一事实源同步到 Figma,最终做到“所有 UI 变更先改 DIM,再由 DIM 驱动 Figma”。
核心文件包括:
- skills/ui-figma-pixel-lock/SKILL.md
- skills/ui-figma-pixel-lock/references/design-intent-model.md
- exam/tokens.json
- exam/components.json
- exam/screens.json
- scripts/preflight_dim.js
- tmp_figma_draw_dim.js
一句话定义这条链路
这套流程的本质是:
自然语言需求
-> DIM(结构化设计意图模型)
-> Figma 页面与组件
-> 回查校验
这里的 DIM,在当前工程里指的是 Design Intent Model,也就是“设计意图模型”。它不是设计稿图片,也不是最终代码,而是一个足够结构化、足够精确、足够可计算的中间层。
在这个工程里,DIM 被拆成 3 个文件:
tokens.json:全局视觉变量components.json:组件结构与状态screens.json:页面级布局与节点树
为什么一定要先有 DIM
如果直接从一句需求去画 Figma,通常会有 4 个问题:
- 描述里存在隐含值,比如字号、圆角、按钮高度、边框颜色没有写全。
- 同一个元素在不同页面里会被人“凭感觉”画出微小差异。
- 后续改版时,无法确定哪个值是规范,哪个值只是临时修补。
- Figma 会慢慢变成事实源,设计规范反而失效。
DIM 的作用就是把这些不确定性提前消灭掉。只有当描述被压缩成结构化、显式、无歧义的数据后,Figma 才能被稳定地“生成”出来,而不是被一次次“手画”出来。
当前工程里的 DIM 结构
1. tokens.json
tokens.json 负责描述设计中的全局常量,例如:
- 品牌色
- 文本色
- 页面底色
- 间距体系
- 圆角体系
- 字体体系
- 阴影体系
例如当前 exam/tokens.json 里已经定义了:
color.brand.primary:微信绿#07C160spacing.s16、spacing.s24等间距 tokenradius.r8、radius.r12、radius.pilltypography.titleMd、typography.bodyMd、typography.caption
这一层解决的是“视觉数值必须唯一”的问题。
2. components.json
components.json 负责定义组件蓝图。它并不画页面,而是描述“页面里会重复使用的那类元素”。
当前 exam/components.json 已定义了这些组件:
cmp.nav-barcmp.exam-cardcmp.progress-barcmp.button-primarycmp.question-option
每个组件描述了几类信息:
- 基本尺寸
- padding / gap / 排布方向
- 基础样式
- 可承载的 slot
- 支持的状态
states
例如 cmp.question-option 不只是一个矩形框,而是一个带:
- 固定宽度
- 最小高度
- 内边距
- 文本样式
- 选中/禁用/正确/错误等状态
的题目选项组件。
这一层解决的是“重复 UI 必须可复用、可变体化”的问题。
3. screens.json
screens.json 负责把组件实例和纯文本节点真正拼成页面。
当前 exam/screens.json 里有 2 个页面:
screen.homescreen.question
它里面定义了:
- 页面尺寸
- 页面背景色
- 每个节点的类型
- 节点坐标
- 节点引用的组件
- 节点传入的 props
例如:
- 首页里的
home.card.upcoming是一个cmp.exam-card - 做题页里的
question.option.b是一个cmp.question-option - 做题页里的
question.title是一个纯文本节点
这一层解决的是“具体页面到底由哪些元素构成”的问题。
从自然语言到 DIM:应该怎么拆
这一步是整条链路里最关键的认知层工作。原则是:先把需求翻译成视觉事实,再把视觉事实翻译成 DIM。
例如用户说:
做一个微信风格的考试类小程序首页,上面有标题,中间是考试卡片,底部是继续答题按钮。
这句话不能直接进 Figma,因为还缺很多信息。正确做法是先补齐这些维度:
1. 页面级信息
- 目标设备尺寸是什么
- 是微信小程序风格,还是完全复刻微信原生页面密度
- 顶部导航高度是多少
- 页面背景色是什么
2. 组件级信息
- 考试卡片宽高是多少
- 卡片圆角和阴影是什么
- 卡片内标题、元信息、进度条、按钮的相对位置是什么
3. 文字级信息
- 字体是
PingFang SC - 字重分别是多少
- 行高多少
- 文本垂直居中规则是什么
4. 状态级信息
- 选项的
default / selected / correct / wrong / disabled怎么表达 - 主按钮的
default / pressed / disabled怎么表达
只有这些值都显式化,DIM 才算合格。
这个工程里对 DIM 的硬约束
当前 Skill 已经把 DIM 输入约束写得很明确,见 skills/ui-figma-pixel-lock/SKILL.md。
它要求 DIM 必须显式提供:
- Frame size and grid
- Typography values
- Corner radius / stroke / shadow / blur
- Spacing and alignment rules
- Interaction states
它还定义了一些“禁止自由发挥”的规则:
- 不允许凭审美补值
- 不允许随意改 token 名
- 不允许替换字体
- 不允许手动调整 spacing 来“看起来差不多”
- 除非明确说明,否则文本要垂直居中
- 默认使用
390 x 844画板 - 每次改动必须先改本地 DIM,再从 DIM 更新 Figma
这里最重要的一条是:
Never update Figma first.
也就是:Figma 只是渲染结果,不是源头。
DIM 写完以后,为什么还要做 preflight
DIM 不是只要存在就够了,还要确认它是“这次确实被改过、而且结构合法”的。
当前工程里的 scripts/preflight_dim.js 就是这一步的前置检查脚本。它主要做三件事:
- 检查
exam/tokens.json、exam/components.json、exam/screens.json是否存在。 - 检查这三个文件是不是合法 JSON。
- 检查当前 Git diff 里,这次是否真的改到了
exam/*.json。
它的目的不是“验证美观”,而是防止出现这类错误流程:
- 人在 Figma 里改了 UI,但忘了回写 DIM
- 人准备执行同步,但本次根本没有 DIM 变化
- DIM 文件被手改坏了,JSON 已不合法
也就是说,preflight_dim.js 是把“流程纪律”程序化。
从 DIM 到 Figma:这个工程是怎么画的
真正把 DIM 变成 Figma 节点的入口是 tmp_figma_draw_dim.js。
虽然它现在文件名里有 tmp,但逻辑上它已经是一个完整的 DIM 渲染器,主要承担 6 层职责。
1. 读取 DIM
脚本启动后先读取:
exam/tokens.json
exam/components.json
exam/screens.json
并把它们解析成内存对象。
2. 解析 token
脚本内部有一套 resolver,会把 token 路径解析成具体值,例如:
color.brand.primary-> RGBAradius.r8-> 数值typography.bodyMd-> 字体对象
这一步的价值是:页面绘制代码不直接写死颜色和字号,而是统一通过 token 引用,保证 Figma 和 DIM 的视觉数值始终一致。
3. 连接 Figma MCP
脚本会通过 cursor-talk-to-figma-mcp 连接 Figma,并先 join_channel 到指定通道。
这一层的作用是把“结构化 DIM”翻译成一连串可执行的 Figma 操作,例如:
create_framecreate_rectanglecreate_textset_fill_colorset_stroke_colorset_corner_radiusmove_noderesize_node
也就是说,DIM 本身不懂 Figma API,但渲染器懂。
4. 读取当前 Figma 页面,决定写入模式
这是当前 Skill 里一个很重要的安全设计。
写入模式分两种:
appendreplace
默认是 append,不是 replace。
含义是:
append:即便 Figma 里已有同名页面,也要新建一个不重叠的页面放进去replace:只有用户明确要求覆盖、刷新、同步替换时,才允许在原位置清空并重画
当前 Skill 对这件事要求非常严格,因为它要避免把已有设计稿误删掉。
5. 为页面寻找不重叠位置
脚本会先读取当前 Figma 文档顶层 frame,然后计算一个不会与现有 frame 重叠的位置。
这一步对应 Skill 中的规则:
- 新页面必须放到可见区域
- 不能压在已有页面上面
- 同名 frame 不是覆盖许可
这个机制解决的是“同步一次把旧稿盖掉”的事故风险。
6. 按 screen 节点逐个绘制
drawScreen() 会遍历 screens.json 中的节点,并根据不同类型分发给不同绘制函数。
例如当前工程里已经覆盖了:
drawNavBar()drawExamCard()drawQuestionOption()drawPrimaryButton()drawTextNode()drawRectNode()
也就是说,screens.json 只是告诉系统“这里有一个 cmp.question-option 实例”,真正决定怎么画的是渲染器里的实现。
当前工程中的页面是怎么落到 Figma 的
以当前 exam 目录里的数据为例:
首页 screen.home
这个页面由:
NavBar- 两张
ExamCard
构成。
渲染时流程大致是:
- 创建页面 frame
- 用页面背景 token 填充底色
- 在顶部绘制导航栏
- 在指定坐标绘制第一张考试卡片
- 再绘制第二张考试卡片
考试卡片内部又会继续细分为:
- 卡片背景
- 标题
- 元信息
- 进度条轨道
- 进度条实心条
- 按钮底
- 按钮文字
做题页 screen.question
这个页面由:
NavBar- 题干文本
- 4 个选项组件
- 一个主按钮
构成。
在 drawQuestionOption() 里,脚本会根据 state 决定:
- 选中态是否使用
color.brand.primarySurface - 边框是否切换到
color.brand.primary
这就说明:组件状态并不是 Figma 里事后人工改出来的,而是从 DIM 里明确定义后被程序渲染出来的。
真正稳定的流程应该怎么跑
如果把这件事变成工程规范,那么一次标准的 UI 变更应该按这个顺序执行:
第 1 步:接收自然语言需求
输入可能是:
- 做一个新页面
- 修改某个组件状态
- 调整页面尺寸
- 改按钮圆角或色值
这一步不要直接动 Figma。
第 2 步:把需求翻译为 DIM 变更
按变化范围修改对应文件:
- 全局视觉变化改
tokens.json - 组件蓝图变化改
components.json - 页面内容和实例排布变化改
screens.json
如果新需求涉及多个层级,允许同时改多个 DIM 文件。
第 3 步:跑 preflight
执行:
node scripts/preflight_dim.js
如果只是验证结构而不要求本次一定有 DIM diff,可加:
node scripts/preflight_dim.js --allow-no-dim-change
通过这一步,确保:
- 文件存在
- JSON 合法
- 本次修改确实已经落到 DIM
第 4 步:连接 Figma,并选择同步模式
如果需求是“把新页面加到 Figma 里看看”,就用 append。
如果需求是“我确认要覆盖当前同名页面”,才用 replace。
这个决策非常重要,因为它决定了同步器是“保守新增”还是“原地刷新”。
第 5 步:执行 DIM -> Figma 同步
由 tmp_figma_draw_dim.js 执行实际绘制。
逻辑上它会:
读 DIM
-> 解析 token
-> 连接 Figma MCP
-> 读取现有页面
-> 计算放置位置
-> 创建 frame / rect / text
-> 应用颜色 / 描边 / 圆角 / 尺寸 / 位置
第 6 步:做零漂移校验
当前 Skill 里定义的校验目标包括:
- token 值一致
- 组件属性集合一致
- 页面节点树一致
- frame 尺寸一致
- 文字样式元组一致
- 填充、描边、效果一致
- 文本默认垂直居中一致
- 新增页面不得覆盖旧页面
只要有一项不一致,结果就应该是 FAIL,而不是“差不多可以”。
为什么这套流程比“先画 Figma,再整理规范”更稳
因为它把 UI 生产过程拆成了两个职责清晰的层:
- DIM 负责描述事实
- 同步器负责渲染事实
一旦职责分清,后续好处非常明显:
- 改 UI 时,不需要猜哪个 Figma 值才是准的。
- 相同组件在多个页面里天然保持一致。
- 可以对 DIM 做版本控制、代码评审、差异对比。
- 可以把“设计变更”纳入工程流程,而不是留在手工软件里。
- 可以持续扩展同步器,让更多组件和布局规则自动化。
当前工程还可以继续补强的地方
虽然主链路已经有了,但如果要把这套方案真正做成稳定生产流,还建议补 5 类能力。
1. 把 tmp_figma_draw_dim.js 正式化
当前它已经是核心执行器,建议后续改成更正式的命名,例如:
scripts/sync_dim_to_figma.js
并补:
- 参数说明
- 日志输出格式
- 错误码
- 成功/失败摘要
2. 增加 DIM schema 校验
目前 preflight_dim.js 只检查“是不是合法 JSON”,还没检查“字段结构是否完整”。
后续建议增加 schema 规则,例如:
tokens.typography.*必须含fontFamily/fontWeight/fontSize/lineHeightcomponents[].id必须唯一screens[].nodes[].id必须唯一instance节点必须有ref
3. 增加回读校验
同步完成后,可以再通过 Figma MCP 读取已创建节点,把关键属性反查回来,与 DIM 做比对,形成真正的“闭环校验”。
4. 把组件绘制逻辑继续抽象
当前部分组件是手写绘制函数。后面可以继续往“通用渲染器”方向演进,让更多组件由 schema 自动生成,减少每加一种组件都要写新函数。
5. 增加从文本描述自动生成 DIM 的规则层
现在工程里已经有了 DIM 和同步器,但“自然语言 -> DIM”这一段还主要依赖人工整理。
下一步最值得做的是建立一层明确的生成规范,例如:
- 哪些描述进入
tokens - 哪些描述进入
components - 哪些描述进入
screens - 遇到缺失值时必须中断,而不是猜测
这样才能把整条链路真正变成:
描述 -> 结构化设计意图 -> Figma
而不是:
描述 -> 人工脑补 -> Figma
推荐的团队协作规则
如果这个仓库以后要多人协作,我建议把下面几条写成硬规范:
- 所有 UI 修改先改
exam/*.json,后改 Figma。 - 禁止只改 Figma 不回写 DIM。
- 所有同步前先跑
preflight_dim.js。 - 默认
append,禁止无说明覆盖。 - 只要同步结果和 DIM 有差异,就视为失败。
这 5 条实际上就是把“设计工具操作”转成“工程化流程”。
总结
当前工程已经具备一条很清晰的主链路:
- 用
tokens/components/screens描述 UI。 - 用 Skill 限制 DIM 必须显式、可计算、不可脑补。
- 用
preflight_dim.js保证同步前输入安全。 - 用
tmp_figma_draw_dim.js把 DIM 翻译成 Figma 操作。 - 用 append/replace、非重叠放置、零漂移校验保护现有设计。
如果把这套思路再往前推进一步,把“自然语言 -> DIM”的生成规则也工程化,那么这个项目就不只是“把设计稿画到 Figma”,而是在做一条真正可复用的 UI 生产流水线。
它的目标不是辅助画图,而是把“画图”本身降级为一个可靠的渲染步骤。