截图丢失了Figma的布局树结构信息,LLM只能推测而非理解布局;结构化IR方案figmascope可解决此问题。
Figma 文件是一棵树。Frame 包含 auto-layout 组,组包含组件实例,实例包含文本和填充层。这棵树编码了布局意图:这一行是一个 flex 容器,这张卡片是一个带内边距的盒子,这三个元素是带有 16px 间距的兄弟元素。
截图把这棵树压扁成了一网格像素。LLM 看到的是形状和颜色。它看不到布局结构——它只能推断。推断在两个方向上都是有损的:模型可能重建出视觉上看起来正确但语义上错误的结构(用固定宽度 div 而非 flex 子元素,用绝对定位而非 auto-layout),或者它可能看到结构歧义然后随意选一个。
从 PNG 你无法判断一行水平排列的元素是用 display: flex、CSS Grid、自定义 HStack,还是三个绝对定位的 div 实现的。它们视觉上完全一样。LLM 选了一种。每次运行的选择都可能不同。
LLM 能看到一个带圆角的矩形里包含一些文字和一个图标。但它看不到:
Figma 中的语义存在于层树中:组件名、变体属性、节点类型。Button/Primary/Large 组件是显式类型化的。截图中,它只是一个带阴影和标签的圆角矩形。模型大多数时候能猜对"这可能是一个按钮"——然后基于颜色猜测"这可能是 primary 变体",这可能与你的设计系统实际命名相符,也可能不相符。
小的不匹配会累积。ghost 按钮被渲染为 outlined 按钮。tooltip 被渲染为模态框触发器。disabled 状态被渲染为 active。每个错误都是一次截图推断步骤,远离了真相来源。
看一张带内边距的卡片截图。内边距是多少?无法得知——除非你测量像素、知道画布比例、知道导出分辨率,还得做计算。LLM 数学做得很差——它估计、四舍五入,而且它完全不知道你的间距系统用的是 8px 基准网格还是 4px 还是自定义的。
所以它猜。它生成 padding: 12px 而设计说是 16。它生成 gap: 8px 而设计说是 12。这些数字单独看是合理的但实际是错的——如果你的设计系统使用间距 token 如 spacing.md 或 Spacing/400,LLM 完全不知道这些。它硬编码了字面量,一旦任何东西变化就会与你的系统偏离。
LLM 没有在幻觉。它在做任何只有截图的人会做的事:猜测。只是当猜测出错时你感到惊讶,因为你在 Figma 文件中明明一直能看到正确答案。
你的设计师把背景设成了 #7F5CFE。在 Figma 中,这个 hex 绑定到了一个变量:color/brand/primary。这个绑定是有意义的——意味着这个颜色参与主题系统,意味着暗色模式会替换它,意味着如果品牌色变了你只需更新一个变量,所有实例都会更新。
在截图中:它是紫色。LLM 生成 background-color: #7F5CFE。Token 关系没了。你的代码库里现在有一个硬编码的 hex,它永远不会跟随你的设计系统。把这个乘以屏幕上每个组件。
同样的问题也适用于字体排版比例、圆角和阴影定义。维护良好的 Figma 文件中,每个值都可能是命名 token。截图中的每个值只是一个数字。
良好组合的屏幕会复用组件。四个产品卡片是同一个 ProductCard 组件的四个实例。导航栏中的头像和评论中的头像都是 Avatar/Medium 的实例。这对代码很重要:你想要一个 React 组件,而不是四份会逐渐分化的人写变体。
从截图看,LLM 看到四个视觉上相似的矩形。它可能生成一个可复用组件——也可能生成四段几乎相同的 JSX 代码,因为它没注意到它们是同一个东西。图片中没有信号告诉它哪种是对的。
结构化上下文在每个实例节点上携带 componentId。Agent 知道:这四个节点都是 ProductCard。生成一次,用不同 props 渲染四次。这才是你想要的输出。这才是你无法从像素得到的。
你在三个不同屏幕上都放了"Continue"按钮。这三个是同一个字符串的实例,还是设计师独立写的?在结构良好的 Figma 文件中,它们引用同一个字符串 key。这意味着一个 i18n 条目,一次变更全局生效。
在三张截图中:LLM 三次生成硬编码字符串。如果你在构建一个国际化应用,你现在有三个字符串需要查找替换,而不是一个。事情虽小,在真实代码库中会成倍累积。
模型没有上一次运行的记忆。每次你粘贴同一张截图,它都从零重建结构。重建是概率性的——这意味着同样的截图 + 同样的提示词 + 同样的模型,在不同运行中可能产生可测量的不同输出。同样的设计,不同的代码。不同的组件名、不同的 className 模式、不同的布局选择。
这不是模型 bug。这是概率模型在约束不足时的预期行为。截图提供的约束不足。模型填充了空白——空白每次被填充的方式都不同。
你可以通过更长、更详细的提示词部分解决这个问题——"使用 Tailwind,使用 8px 网格,使用这些组件名……"——但那样你就手动指定了本该在设计文件中就存在的结构。你在做工具应该做的提取工作。
使用截图做设计到代码交接的团队会遇到同样的墙:输出不可复现。两个开发者,同样的 Figma 截图,独立向 Claude 提问——他们得到不同的组件结构、不同的 className 模式、不同的嵌套决策。现在你有两个代码库,视觉上看起来一样但架构上不一致。
这让代码审查更难。让重构更难。让设计系统合规审计不可能。你无法 diff"agent 从这个设计中生成了什么",如果答案每次运行都不同的话。
结构化上下文通过修复输入来修复可复现性。确定性输入包——同样的 JSON,同样的节点 ID、组件名、token 值和空间关系——在不同运行、不同 agent、不同开发者之间产生更加一致的输出。不是完全确定性:模型还是概率性的。但当结构被指定而非被推断时,方差急剧下降。
以一个产品卡片为例:图片、标题、副标题、价格、一个"Add to cart"按钮。以下是每种输入给 agent 的内容:
截图输入:顶部一张图片的矩形、两行文字、一个数字、一个按钮。颜色是推断的。内边距是估计的。这是组件还是一次性的未知。按钮变体从颜色推断。间距系统未知。
IR 输入:节点类型 FRAME,名称 ProductCard,componentId 链接到组件定义。Auto-layout,垂直方向,16px 间距,16px 水平内边距,12px 垂直内边距。子节点:IMAGE(宽度填充,高度固定)、TEXT(stringRef.key: "product.title",样式 typography/heading.sm)、TEXT(stringRef.key: "product.subtitle",样式 typography/body.md)、TEXT(填充色 color/price)、INSTANCE of Button/Primary/Medium。背景填充 color/surface.card。圆角 radius/card。
IR 给 agent 一份规格书。截图只给了建议。
我们在几十年前就为源代码解决了这个问题。你不会给 agent 一张代码库的截图然后让它推理架构。你给它代码——结构化的、可解析的、有语义意义的表示。抽象语法树,不是编辑器的图片。
Figma 设计是结构化数据。它们有定义明确的树结构,包含类型化节点和命名值,Figma API 完全暴露了这些。截图工作流之所以持续存在,唯一原因是提取结构并格式化为上下文有摩擦。
figmascope 做的就是降低这种摩擦。你粘贴 Figma URL,导出在你的浏览器中运行,你得到一个 ZIP 包,包含结构化上下文:CONTEXT.md、tokens.json、按屏幕分的 IR、组件清单、字符串清单。Agent 需要的一切,没有一样是从像素推断的。
截图保留用于视觉确认——ZIP 包中包含了 2x PNG 正好为此。用结构做其他所有事。如果你用 Claude Code,完整交接管道演练展示了端到端模式;也有 Cursor 和 Aider 的变体。