Vignette:一个完整项目,从需求到签名安装包
整本指南的实战范例:一款多平台便利贴应用,由迷你主机上的 AI 代理完成设计、开发、托管和发布,只用了一夜加一个上午。附真实的坑和真实的截图。
本文目录
本文核对于 3 个月前,部分命令可能已有变化。 如有出入,请告诉我们。
要点速览
Vignette 是一款停靠在屏幕边缘的便利贴应用,2026 年 8 月底由代理们在本指南的迷你主机上,用一个晚上加一个上午完成了设计、构建、托管和发布。它已上线(网页、macOS、Linux、Android),后端是自托管的 Supabase,配有单元测试、24 个端到端场景和备份。这个故事展示了行之有效的做法(带草图的简报、尽早做出的人类决定、互相核查的多个代理会话),也讲了卡住的地方:绝不凭退出码验收,v0.1 终究只是 v0.1。
请先完成: 用 LLM 厘清一个项目让多个 agent 协同工作
这本指南教您搭建机器、安装代理、界定项目。而这一篇展示这一切通向哪里:一个真实的项目,从第一条消息到可下载的安装包,全程跑在指南里描述的这台迷你主机上。项目于 2026 年 8 月底完成,名叫 Vignette,一款贴在屏幕边缘的便利贴应用。您可以打开它、下载它、阅读它的源代码。没有任何摆拍,这正是它值得拆解的原因。
起点:一份需求书,而不是一句提示词
晚上的需求书比一条消息丰满得多:两张在纸角上信手涂鸦、再用手机拍下的草图,几张喜欢的应用截图,一串精确的要求(「精致、原生、多平台、共享同步、管理后台、默认法语」),一份取名请求,还有一次小小的竞品调研:市面上有什么、卖多少钱、更重要的是缺什么。结论一句话就够:笔记应用都把屏幕当文档,没有一个把屏幕边缘当作一个地方。这正是《用 LLM 界定项目》那一篇的方法:用展示代替描述,说清想要什么结果,而不是怎么写代码。
竞品调研在项目中途还派上了第二次用场:从一款仅限 macOS 的付费竞品那里,相中并借鉴了两个好点子(键盘快捷键、删除后十秒内可撤销),也顺势立住了相反的定位:多平台、开源、可自托管。看竞品不是抄袭,是知道自己站在哪里。
写第一行代码之前,人做了三个决定:名字(从候选里选中 Vignette)、桌面端用 Tauri v2 而不是 Electron(安装包轻十倍,网页应用直接复用)、后端用自托管 Supabase 而不是云服务。代理逐项给出论据,人来拍板,这三个选择一直撑到最后。
技术选型:语言、架构,和一套裁剪过的栈
语言和架构的每个选择,都是为了让代理快速找到北。pnpm 单仓库,从头到尾严格 TypeScript;一个 core 包装着纯业务模型(便利贴排序、状态规则、Markdown 导入),不含一行界面代码,由 Vitest 测试。四周是彼此独立的应用:React 19 + Vite 的网页端(弹簧动画交给 Framer Motion)、静态官网、管理后台、一个零依赖的 Node 管理 API,以及 Tauri v2 的原生外壳,Rust 只做网页做不到的事(窗口、托盘、全局快捷键、签名更新)。一个刻意的细节:不用 Tailwind,而是一套基于 CSS 变量的自制设计系统,因为这个项目的手写美学装不进任何现成的格子。所有东西共用一个域名:Caddy 按路径分发应用、官网、后台和 API,CORS、TLS 和日子都简单了。
自托管 Supabase 通常要跑十来个容器。Vignette 只跑六个:Postgres、认证(GoTrue)、REST API(PostgREST)、实时同步、一个零依赖的 Node 管理 API,前面站着 Caddy。Kong、Studio 和分析组件全部裁掉。结果是大约 450 MB 内存,在一台还托管着其它一切的迷你主机上,这个占用很合理。
安全的根基是 Postgres 的行级安全(RLS):每张表都带着规则,逐行决定谁能读、谁能写。一个账号读不到另一个账号的便利贴,就算直接调 API 也不行。这种保证要靠验证,不能靠假设:一套测试每晚对真实实例重放十条不变量(「入侵者读不到」「受邀编辑者删不掉」),正如《复查、审计、加固》所建议的。
视觉设计:要像真的便利贴
视觉方向来自需求书里的截图:淡彩标签停靠在屏幕边缘、手写字体(Caveat)、弹簧动画、十二种颜色。中途一条用户反馈定下了金规矩:任何东西都不许从边缘剥离。悬停的标签向页面内侧变大,而不是脱开边框;展开的便利贴牢牢焊在屏幕边上。都是细节,但可信感全靠它们。
桌面端把概念做到了头:一张便利贴可以钉成一扇真正的无边框窗口,永远置顶,摆在桌面上。碍事的时候,它自己收成贴着屏幕边缘的小标签,就像随手挪开的真便利贴。
托管:完全按照本指南的线路,无一例外
一个请求的路径严格照抄网络章节:域名走 Cloudflare 隧道(路由器上不开任何端口),落到 Caddy 再分发到对应服务,Postgres 永不暴露。备份照搬《Git、GitHub 与备份》的方针:每晚一份 SQL 转储、NAS 上一份副本、每周再镜像一份到别处。监控在不变量被破坏后六小时内通过 Telegram 报警,而且警报本身在某天早上 8 点 35 分被故意触发过一次,因为从没响过的警报不算数。
有两个托管细节值得照抄:
已完成 0 步,共 2 步 勾选记录只保存在此浏览器中。
-
SMTP 是真正可选的
没有邮件服务器一切照常:后台创建的账号立即可用,「忘记密码」链接自动隐藏,丢失的密码由管理员两下点击就能重置。如果强制要求 SMTP,一半想自托管的人就被挡在门外了。
-
应用只凭一个网址就能接入
每个实例都公开一个小小的发现端点,描述自己的配置。原生应用首次启动时会问「您的便利贴住在哪里?」:官方实例、您自己的实例(贴上网址,其余自动发现),或者完全本地、无服务器的模式。
质量:单元测试、用户测试,和记忆
三张叠起来的网守着这个项目,各自接住别人漏掉的东西。
第一张网很经典:core 的单元测试(Vitest)锁死模型,一套 24 个场景的端到端测试在真实浏览器里重放每条用户路径:登录、错误密码、创建、颜色、便利贴堆、提醒、两个账号之间的实时共享、导入、导出、设置、移动端。一条命令,五分钟,出结论。有趣又有教育意义的是:它第一次运行时的四个失败全是测试自己的 bug,不是应用的;一个写得太宽的选择器点了「导入」,还以为自己点的是「导出」。测试也需要调试。
第二张网是用户测试,手工作坊版:用户睡醒,把所有东西试一遍,然后成批发来感想(「图标看不清」「小部件是空的」「菜单太挤」「这里剥离得很怪」)。每条意见都按工单处理:复现、修复、重测、重新部署,常常十分钟之内。其中两条意见变成了刻进项目记忆的设计规则。再全的自动化测试,也替代不了一个觉得按钮丑的人。
第三张网是记忆,就是《记忆文件》里说的那种:仓库里有一份指令文件,累积着实打实踩过的坑(连同解法,保证同一个坑不交两次学费);还有一份跨会话的持久记忆,装着决策、用户偏好和教训。安全收尾:一次专项检查确认 git 历史里没有任何机密值残留(搜的是真实值的片段,不是变量名)、只暴露了那个公开发现端点、注册在服务端关死、基础安全响应头都已就位。由代理复查,而非外部审计师:这层区别写在下一节。
三个代理、两台机器、密钥零出行
迷你主机没法为 macOS 编译。于是第二个代理会话跑在一台 MacBook 上,两边的协作方式正如《让几个代理协同工作》所写:Mac 上的会话负责构建、投递安装包、报出校验和;迷你主机上的会话核对校验和、签名、发布、更新校验清单。一夜之间,七个 macOS 构建走完了这条流水线。
其实他们不是两个,是三个。一个常驻的编排会话扮演舞台监督:掌管敏感凭据(Cloudflare 隧道,改错一步全线瘫痪)、布设监控和探针、分发工具与守则(缓存清除令牌、每个仓库的门规)、在用户去别处留言时转达反馈,最重要的是:核实其它会话的申报,而不是听信一面之词。当构建会话宣布改动了个人网站时,编排者先去线上把页面查了一遍,才把申报归档。建造者盖楼,它管着整个片场。
这段协作最精彩的一幕:给更新签名的密钥从不离开迷你主机。轮到给 Mac 的产物签名时,macOS 会话拒绝在机器之间搬运私钥,并提出了更好的办法:把文件取回来,在本机做分离式签名。是代理收紧了安全流程,而不是反过来。
实打实踩过的坑
只讲成功的项目故事教不会您任何东西。当晚的三个 bug 值得专门一说,因为它们共享同一个教训:
- 白窗口。 第一个 macOS 构建打开是一片空白:原生包里的资源路径错了。而编译的退出码,完美无瑕。
- 黑图标。 转换 SVG 标志的命令行工具把渐变渲染成了黑色,一声不吭。送进 Dock 的「亮黄色」图标是一团黑疙瘩。
- 空便利贴。 钉住的窗口加载了主题却永远加载不出数据:启动同步的模块只被登录界面调用,而这扇窗口根本不经过登录界面。两个代理各自独立追出了同一条因果链,然后才动手修。
这个教训如今成了两个会话之间的工作守则:永远不要凭退出码验收。把程序跑起来,看画面,必要时量像素的颜色。那一夜有三次,「已经成功」的东西其实不能用。
困难,以及这个实验的边界
没有硌手的部分,故事就可疑了。这里是卡壳的地方,以及这个例子证明不了的事。
先说困难。原生应用的迭代很重:应用里每一处看得见的改动,都意味着三个平台重新编译、重新签名、重新投递;一夜七个 macOS 构建是症状,不是壮举。远程验证有边界:通过 SSH,没有任何代理能点击 macOS 的窗口、看到菜单栏,所以有些检查(全局快捷键、某个链接是否唤起 Safari)只能落回到人身上。MacBook 在传输中途睡着了三次,直到一条限时的 caffeinate 命令解决问题:准备最不充分的那台机器,会变成所有人的瓶颈。最有教育意义的坑:一条测错了场景的自动化证据,会给人虚假的安心。钉住的便利贴有它的显示证据,但那是在本地模式下拍的;bug 只活在联网模式里,最后是用户的眼睛看见了它。
再把边界说透。「一夜四个平台」产出的是扎实的 v0.1,不是打磨多年的产品:没有真实负载下的用户,没有验证过的扩容能力,无障碍支持还不完整。截至 2026 年 10 月 1 日,iOS 停在模拟器(需要 Apple 开发者账号),Windows 还在等一台能编译的机器,小组件和点对点同步都还躺在路线图上。安全由代理梳理过,但没有经过外部审计:RLS 每晚都在被测试,可人类渗透测试完全是另一回事。最后,这里没有任何东西能替代产品眼光:没有清晨那一连串反馈,这款应用会停留在「处处正确、处处差一点」。代理放大方向,但不提供方向。
这个例子说明什么,不说明什么
用数字说话:一夜加一个上午,四个平台上线(网页、macOS、Linux、Android),安装包签名、更新内置,外加管理后台、备份、监控,以及一套在浏览器里重放全部用户路径的 24 场景测试。
它不说明的:奇迹。这背后是一台准备就绪的机器(整本指南)、一份带截图的需求书、每个岔路口的人类决策,以及清晨源源不断的反馈(「图标看不清」「小部件是空的」「这个菜单太挤了」),每一条都在几分钟内被修复、测试、重新部署。代理干活,方向盘在您手里。人这一侧的完整故事,写在 ulrichrozier.com/vignette。
本文全部命令
常见问题
Vignette 是用哪些技术做出来的?
一个采用严格模式 TypeScript 的 pnpm monorepo:core 包存放业务模型,用 Vitest 测试;网页应用用 React 19 和 Vite;原生外壳用 Tauri v2,Rust 只负责网页做不到的事。后端是自托管的 Supabase,精简到六个容器:Postgres、身份认证、REST API、实时服务、一个用 Node 写的小型管理 API,以及 Caddy,由它在同一个域名下为应用、网站、后台和 API 分配路由。
桌面应用为什么选 Tauri 而不选 Electron?
在 Vignette 这个项目里,Tauri v2 胜出有两个理由:安装包轻了十倍,网页应用可以原样复用。Rust 只用来做网页做不到的事,比如窗口、系统托盘图标、全局快捷键和带签名的更新。这个选择在写下第一行代码之前就由人拍板,并且一直沿用到最后。
在 Linux 迷你主机上工作,怎样编译 macOS 应用?
迷你主机无法为 macOS 编译,所以在一台 MacBook 上运行了第二个代理会话。Mac 上的会话负责构建应用、放下安装包并公布它的指纹;迷你主机上的会话核对指纹,然后签名、发布并更新校验和。签署更新的密钥从未离开迷你主机:文件被传回来,在本机完成签名。
用 Supabase 时,怎样防止一个用户读到另一个用户的数据?
Vignette 依靠 Postgres 的行级安全(Row Level Security):每张表都带有规则,逐行决定谁能读、谁能写,即使有人直接调用 API 也一样。这种保证经过实际验证:每天夜里,一套测试会针对真实实例重跑十条不变量,比如「入侵者无法读取」或「受邀的编辑者无法删除」。
自托管 Vignette 需要邮件服务器吗?
不需要,SMTP 是可选的。没有邮件服务器时,从后台创建的账号立即生效,「忘记密码」链接会自动隐藏,丢失的密码由管理员点两下就能重设。如果强制要求邮件服务器,会把一半想自托管的人挡在门外。
本文涉及的术语: 开源(对比开放权重)MarkdownAPI容器RAM(内存)Docker 镜像Git编排者CLISSHLinux
发现错误?
命令失效了,价格变了?
这些工具每个月都在变。请告诉我这篇文章哪里不对,我会改正并更新日期。