← 返回博客

画布曾经藏起来的那一层版式

0.8.0 preview 下的一条社区回复,点出了 agent 原生设计背后真正的问题:如果画布不再是工作单位,用户又该如何理解版式?

画布曾经藏起来的那一层版式

一条有用的社区回复不会要一个更大的按钮。它会点出那个缺失的层。

这就是 Open Design 0.8.0-preview 讨论 下发生的事。发布帖主张了两个转变:别再把画布当作主要的工作单位,并让 agent 成为一等的设计工作者。一条回复认同这个方向,然后指向了难的那部分:当画布消失时,用户在能够有把握地编辑 agent 做出来的东西之前,仍然需要一种方式去理解它。

那条回复里的说法是「版式理解层(Layout Understanding Layer)」。这是个好名字,因为它拒绝了那个偷懒的答案。agent 原生设计不能意味着「相信那张截图」。它需要一个可读的产物模型:分节、意图、可编辑的部分、稳定的引用,以及建议的编辑动作。这篇文章是认真对待那条回复的一次尝试——勾勒这样一个层可以承载什么、它在 Open Design 里应该住在哪里,以及为什么它是一条贡献路径、而非一项路线图承诺。

画布给了设计师空间上的笃定

Figma 的画布不只是一块绘图表面。它也是一块解释表面。你可以缩小看页面层级,点击一个 frame,检视约束,重命名图层,并理解工作的一块从哪里结束、另一块从哪里开始。

当画布消失时你真正失去的是什么

这种理解在它还在时很容易被低估,直到它没了。一个生成出来的落地页在沙箱预览里看起来可以是正确的,却仍然难以指挥。问题不在像素——在于人与 agent 之间缺少一套共享的词汇。

「让 hero 更自信一点」只有在 agent 和人对 hero 是什么达成一致时才有用。「收紧 pricing 这一节」只有在产物跨修订版携带着稳定的分节身份时才管用。没有这些,每一条指令都退化成指指点点:设计师用位置来描述一个区域(「从上往下第二块」),agent 从渲染输出里去猜,而下一次重新生成又悄悄把一切重新编了号。画布过去把这个成本默默地吸收掉了。它替你给各部分命了名,在你工作时让那些名字保持稳定,并让你不用描述就能选中其中一个。

即便工作单位错了,那份清晰仍值得留下

这是 #DeFigma 论点里需要小心的部分。对一个 agent 原生的系统来说,画布也许是错误的工作单位——它假定的是一个人在拖矩形,而不是一个 agent 在写文件——但人们从画布那里得到的清晰仍然是有价值的。错误会是把「丢掉画布」和「丢掉画布所提供的那份理解」当成同一个决定。它们不是。Open Design 必须把那份清晰挪进产物模型里,而不是把它扔掉。版式层就是这次挪移的名字。

Open Design 已经有了正确的原语

这个提案契合 Open Design 的原因,在于这个项目本就是围绕显式契约构建的。一个 skill 是一个 SKILL.md 文件。一个设计系统是一个 DESIGN.md 文件。一个插件添上一个 open-design.json 伴随文件。系统里没有任何东西是你必须逆向工程的二进制 blob;契约就是文本,而文本是 agent 和人类都能读的东西。机制在 31 个 skill、72 个 system:Open Design 库是怎么运作的 里讲过,产品论点在 我们为什么把 Open Design 构建成一个 skill 层、而不是一款产品 里。

版式层应当遵循同样的模式。它应当是 agent 能读的文本、UI 能渲染的状态、另一个 agent 能复用的元数据。如果它没法同时满足这三点,那它就是错误的形状。

用仓库的话说,这指向三个面:

它应当承载什么
产物清单分节、组件、资源和生成文件的稳定 ID
插件快照是哪个 skill、设计系统、输入和流水线阶段产出了这个产物
审查 UI / 无头输出一张版式地图、可编辑的方面,以及建议的编辑意图

重要的约束:这个层不应变成第二块专有画布。要避免的失败模式是给 Figma 的场景图换个名字重建一遍——一个丰富的、应用专属的结构,只有 Open Design 的 UI 能渲染它,而你一离开这个应用它就死掉。版式层应当反过来是一张随文件一起走的、朴素的产物地图,这样一个贡献者能在文本编辑器里读它,另一个 agent 也能在没有 SDK 的情况下消费它。

一具线框版式骨架作为它自己可选中的一层、从一块空白画布上方抬出,在近白色的编辑风底纹上被绿色框选中
版式层是画布保持隐含的那个结构——被拽出来放到 agent 和人类都能读到的地方。

版式层一个实用的形状

这里是能让 agent 原生设计感觉不那么不透明的最小版本:

  1. 每个生成出来的产物都拿到稳定的语义 ID:heroproof-strippricingfaqfinal-cta
  2. 每个 ID 携带一句意图:「在一屏里说清产品承诺」,而不是「顶部那一节」。
  3. 每一节列出可编辑的方面:文案、密度、布局、颜色、媒体、动效、数据源。
  4. 每个生成文件回链到产出它的那个分节 ID。
  5. 每一轮审查发出建议的编辑意图:「缩短 hero 标题」、「提高 pricing 卡片的对比度」、「拆分 testimonial 块」。
  6. UI 把这渲染成一个导航器,而无头用户拿到的是同样结构的 JSON 或 Markdown。

一份清单实际可能长什么样

把它写下来的意义在于:这个结构平平无奇——这恰恰是为什么它能成为一份公开契约、而非一个私有花招。一份生成出来的落地页的清单可能读起来像这样:

{
  "artifact": "landing/index.html",
  "producedBy": { "skill": "magazine-poster", "system": "linear", "stage": "compose" },
  "sections": [
    {
      "id": "hero",
      "intent": "Explain the product promise in one screen",
      "editable": ["copy", "density", "media"],
      "files": ["landing/index.html#hero", "landing/hero.css"]
    },
    {
      "id": "pricing",
      "intent": "Let a visitor self-select a plan without scrolling back",
      "editable": ["copy", "layout", "data-source"],
      "files": ["landing/index.html#pricing", "landing/pricing.json"]
    }
  ],
  "suggestedEdits": [
    { "target": "hero", "intent": "shorten headline to one line" },
    { "target": "pricing", "intent": "drop from three plans to two" }
  ]
}

这些 key 没有一个是奇异的。sections 是一个列表,editable 是一个枚举,files 是一个反向引用。价值不在 schema 有多聪明——而在于这些 ID 能在一次重新生成中存活下来,所以那同一个 pricing 块在 agent 重写它两次之后仍然是 pricing

这就足以让一位设计师说出「把 pricing 从三个套餐改成两个」,也足以让一个代码 agent 在不靠像素去猜的情况下打补丁到正确的文件上。指令解析到一个分节 ID,分节 ID 解析到一组文件,编辑就落在了它本该落的地方。

为什么这是一条贡献路径、而非一项功能请求

它还给了社区一条干净的贡献路径。一个贡献者不需要重新设计整个产品才能在这里帮上忙。他们可以写一个为某一种产物类型发出清单的 skill、一个提出编辑意图的审查原子、一个添上某个字段让其他 skill 能读的清单扩展,或者一个断言分节 ID 跨重新生成保持稳定的测试用例。这其中每一个都是一处小的、可合并的改动,让某一个输出不那么不透明——并且因为这个层是纯文本,两位分别在做一套 deck 和一个移动端屏幕的贡献者不必去协调一种共享的二进制格式。版式层成了一份公开契约,而不是埋在应用内部的一项私有能力。

接下来该做什么

如果这是你想攻克的那类问题,就贡献一个让某一个产物更容易检视的小 skill 或插件。从一个具体的输出起步:一个落地页、一套 deck,或一个移动端屏幕。添上稳定的分节 ID,描述可编辑的方面,把它们作为 JSON 或 Markdown 与产物一并发出,然后开 PR 时附上一个前/后产物,好让审查者看到一个可检视的层带来的差别。

这仍然是一个方向、而非一项已交付的功能——现在就给它命名的价值在于:那些原语已经存在,所以这项工作是加法、而非一次重写。

贡献一个 skill

延伸阅读


← 返回博客 GitHub · 来源 ↗