最近整理一个做了很久的本地项目时,我发现根目录里同时放着网站、网页原型、原生客户端、图片稿、Figma 和几代 Spec。它们都不是空壳,网站能启动,原型能点,原生端也在继续开发,旧 PRD 甚至比新文档写得更完整。
麻烦也在这里。下一次需求进来,开发者或者 Codex 应该相信哪一份?它如果从页面最全、文件最多的目录开始,很容易在一套已经退出交付的代码里认真工作。
这种混乱是项目一路演化出来的。项目刚开始时,最重要的是尽快把想法做成能操作的东西。研究、文案、页面、图片和脚本放在同一个工作区里,一个人可以从用户反馈直接改到界面,网页也适合快速试流程。那时把东西放在一起,效率很高,也没有必要提前猜哪些内容以后要独立成仓库。
后来目标平台确定了。为了先看信息结构和页面路径,我们又用网页做了一套接近客户端的交互原型。原生客户端开始开发以后,这套网页没有马上删除,它里面的流程、页面状态和视觉尝试仍然可以用来对照。
同一份代码就这样换了几次用途。最早承载实际产品,后来负责试流程,再后来只用于解释历史设计。文件内容未必立刻变化,它在项目里的权威已经变了。旧页面没有删除,是因为它仍能解释流程为什么这样安排,也能帮助对照后来的改动;保留它,不等于继续维护它。
Git 能记录一行代码什么时候修改,却不会主动说明某个目录从今天起只能回看。研究材料、旧方案和图片稿也是一样。它们仍有价值,只是不应继续决定新功能怎么实现。
人可以靠记忆维持这种区别。我知道哪个目录已经停用,哪张图只是候选,哪份文档后来被推翻。Codex 不知道这些背景。一句「把这个页面改一下」,会让它同时搜到网站、网页原型和原生客户端里的同名页面。三份代码都合理,也都能运行。
这种错误通常不会表现成 bug。语法检查和测试可能全部通过,改动却落在已经失效的产品面上。普通工程检查发现不了「代码没错,事实源选错了」。
最彻底的办法是拆仓,把网站、客户端和历史原型完全分开。但项目还在变化时,拆仓会一起改动脚本、路径、引用关系和构建方式。旧材料又需要保留,用来复查过去的设计依据。我们没有立刻搬迁,而是先给各个产品面登记身份。
当前交付的原生客户端标成 active。仍然被设计、测试或研究使用的材料标成 supporting。只允许回看的旧网站和旧原型标成 archived。注册表不写产品规则,只告诉工具默认从哪里进入。
这份注册表也要接受检查。登记的路径必须存在,原生端不能重新依赖 Web 运行时,默认命令只能检查当前产品面,归档目录不能混入正式验收。这样,边界不再依赖每个人是否记得,违反规则时任务会直接失败。
Codex 的任务说明也跟着变了。以前为了防止它漏掉背景,往往会塞进一大段项目历史。现在只需要写清目标产品面、本次允许读取的事实来源、明确排除的归档区域,以及验收要走到哪一级。它读到的材料少了,判断反而更准确。
文档也按用途分开。产品应当怎样,读最新确认和 OpenSpec。当前实际怎样,读原生运行时代码、自动测试和本轮验证证据。仍在探索的内容,读研究材料、图片原型和 Figma 候选稿。某一版走到哪里,读构建、验收和发布记录。
这几类事实不能互相替代。Figma 里出现一个入口,只能说明设计中有这个方向。Flow 可以点击,只能说明演示路径成立。它们不能证明原生端已经处理了相同的数据、异常和平台行为。
网页原型后来只保留流程验证的职责。它修改快,状态也容易组合,但浏览器与原生客户端的滚动、字体、输入框、键盘、安全区和系统控件并不相同。网页里看着正常的页面,到了目标平台可能被键盘遮挡,也可能出现弹层行为不一致、触摸区域过小等问题。
所以网页原型继续负责流程和信息结构,不再作为原生端的视觉母版,也不能证明功能已经实现。
后来我们用图片原型讨论视觉方向。先固定任务和页面状态,再生成或绘制接近目标平台的画面,确认方向后拆布局、对照原生截图,最后回到代码实现。图片适合看构图,但图里的文字可能写错,数据可能只是临时填充,入口也可能尚未确认。颜色和控件看起来接近平台,并不代表运行时可以照搬。
图片原型开始使用后,行为规则才正式进入 OpenSpec。它记录用户能观察到的内容,包括动作的触发条件、页面状态、异常反馈、保存时机,以及当前明确不做的能力。图片和 Figma 可以反复尝试,只要行为没有经过确认并进入 change,就不能进入运行时。
视觉与交互规范负责另一部分内容。它记录目标平台上相对稳定的规则,例如布局层级、组件语义、触摸区域、弹层方式、加载状态和空状态。页面字段与状态后果仍然由 OpenSpec 管,是否已经实现继续看代码和验证证据。规范没有必要重复描述每个页面。
Figma 只负责设计评审,不负责定义产品行为。Foundations 保存基础规则,Components 保存组件,Screens 保存页面,Prototype 只放当前候选,Flow 负责可点击路径,Archive 保存被替代但仍有审计价值的旧稿。
候选稿还需要状态。Exploration 表示仍在探索,Current candidate 是当前评审版本,Approved 表示设计已经确认。Implemented 需要代码和测试,Device verified 还需要设备验证。画板可点击和运行时可用是两件事。
Figma 可以帮助发现 Spec 没写清的地方,确认后再创建或修改 change。方向只能这样走,不能因为画板已经画出来,就让它反过来覆盖已接受的行为。否则只是把「旧网页说了算」换成「最新画板说了算」。
还有一类更细的漂移。状态名、短文案、语义颜色和图标含义会同时出现在原生代码、图片生成脚本和 Figma 中。只改一处,很容易留下两个旧版本。
版本化设计合同用来管理这类事实。它不重复 PRD,也不描述整张页面,只保存确实会被多个载体消费、适合机器读取的内容,例如状态枚举、短文案、语义值、禁用的视觉模式和对应的 Figma 锚点。
更新时先改合同和版本,再由脚本生成原生端数据与确定性的原型快照,随后更新 Figma 评审锚点,并执行自动检查、开发工具验证和设备验证。直接修改运行时常量会产生生成差异;设计稿先改了状态却没有合同,仍然只能算候选稿。
这里没有追求双向自动同步。Figma 里的探索稿如果能随时反写合同,一次未确认的拖动和改字也可能变成产品规则。我们宁愿保留一次明确确认,让合同、生成文件和运行时按固定方向更新。
设计合同也要控制范围。一次性的排版尝试,或者暂时没有第二个消费者的细节,留在设计稿里更合适。只有某个值需要跨运行时、原型和验收,并且存在实际的漂移风险,结构化才有收益。否则治理会从材料散乱走到每改一个像素都要填表。
工作被分成两条路径。
探索路径从问题和反馈出发,经过研究、网页或图片原型,再进入 Figma 的当前候选与 Flow。这个阶段允许推翻和回退,不要求每次尝试都修改主 Spec。
实现路径从确认开始。行为进入 OpenSpec,易漂移的设计事实进入版本化合同,脚本生成运行时数据和原型快照,Figma 保存对应锚点,原生端按合同实现,然后依次完成自动检查、开发工具验证和设备验证。
发布状态也按证据拆开。Implemented、Automated verified、DevTools verified、Device verified、Uploaded、In review 和 Released 分别表示不同阶段。代码写完、测试通过、真机验证、上传和正式发布不能再被一句「做完了」概括。
候选版本还要能对应到具体的运行时版本、构建清单和代码位置。这样可以确认本地验收的内容就是后来上传的内容。工作树里如果保留了其他改动,也要单独说明,不能用一个模糊的「测试通过」替整棵工作树背书。
这套治理不是提前设计好的。网页和原生端并存以后,才需要 active 与 archived。图片稿开始夹带未确认状态,行为才进入 OpenSpec。多个载体里的设计值反复变化,才有了版本化合同。Figma 页面变多以后,候选、确认、实现和设备验证也需要分开标记。
如果重新接手一个类似的工作区,我会先确认当前唯一的产品面,再给历史材料降权。接着分开「应当怎样」「当前怎样」「还在探索什么」和「已经发布到哪里」。等这些边界稳定后,再挑一个容易变化的功能,让 Spec、设计合同、原型、Figma 和运行时完整走一遍。
物理拆仓也不是永远不做。等不同客户端有了稳定的负责人、独立发布节奏和清晰接口,再拆会更合适。边界还在变化时,先把事实来源和默认入口标清,通常比立即搬文件更有用。
一个长期项目更像一张不断堆东西的工作台。早期用过的工具、旧图纸和打样件没有必要全部扔掉,但今天真正生产用哪套工具,必须标清楚。新成员或者 Codex 走进来时,应该知道哪些东西可以继续修改,哪些只能作为参考,哪些记录证明当前版本已经走到了哪一步。
旧代码还在,但它不再说了算。
如果你也在用 Codex 或其他编码 Agent,可以先标出当前产品面、历史材料和每一级验证证据,再考虑增加更多规则。任务边界还需要一个独立、可检查的入口时,可以查看 Stop That Shit 的公开源码 和中文产品说明。它管理的是当前任务授权,不替代这里说的工作区事实治理。