
这是什么
一个桌面应用,编码 agent 和人在这块同样的画布上干活。agent 通过 MCP 接入——一条共享的 whiteboard mcp 启动命令同时服务 Claude Code、Codex、Cursor、Pi 和 OpenCode——然后把它做过的事画出来:时序图、实体关系图、C4 软件地图、调用栈差异、代码窥视,以及从它自己的轨迹里摘出来的引文。这块画布不是自由涂鸦,而是一份由有类型的块组成的文档,一共十四种;每种块都能携带一个写成 head/path#L10-L24 的源码锚点,而每个锚点都会对着这次审查所钉住的那几次提交做校验,于是在时序图上点一个方框,就能在随仓库一起 vendored 的 Code OSS 编辑器里打开它来自的那个文件,语言服务和快捷键都是现成的。一个用 Rust 写的、理解语法的语义化 diff 查看器把噪音收掉——新增的大函数被概括成伪代码,测试与文档改动被折叠隐藏——而一次审查可以分享到另一台机器。这个仓库只有六个星期大:2026-08-18 建立,575 次提交,2,400 个星,十六个 release,MIT。
谁做的仓库挂在 devdotfast 组织下,提交记录被一个账号主导:575 次提交里 292 次是 thesiti92 写的,样本里几乎每一条 pull request 也出自他;其后是 ketan0 的 128 次(116 次署名 Ketan Agrawal,另有 12 次挂在报告中单独列出的第二个账号上)、Sidharth Menon 的 55 次、Milan Bhandari 的 35 次。贡献者列表里有十一个账号,其中两个是机器人。侦察报告没有提供所有者的任何资料——没有显示名、注册时间或仓库数——所以这里除提交记录之外不做任何断言。
它是怎么搭起来的
组成 · 6Whiteboard 是一个包在文档格式外面的桌面应用,大部分结构选择都是这个前提的推论。格式是一串有类型的块——时序图、实体关系图、C4 软件地图、代码窥视、调用栈差异、从 agent 轨迹里摘出的引文——这些块是 agent 通过 MCP 写的东西,而应用是人读的东西。因为每个块都带一个钉在某次具体提交上的源码锚点,一次审查就不是 diff 的快照,而是一组关于代码的断言:写入时校验一次,钉子移动时再校验一次——这也正是为什么代码里有那么大一块是锚点校验、钉子解析和重新钉钉的上报。底下是一份 vendored 的 Code OSS:编辑器、语言服务器和快捷键是继承来的,不是重写的,因为这个应用首先是代码的阅读器,其次才是编辑器。两个界面于是按不同方式做预算:面向 agent 的工具目录是一份字符预算,在公开场合花出去、又收回来;而画布是一种布局,它的代价按每次打开多少毫秒来量。
- packages/review/
- 693 个文件、8.7 MB,产品本身:
app/src是 React 画布与阅读器,src/review-api是文档、store 与工具层,src/server是无头与桌面宿主,src/sharing是审查分享路径,src/review-import是旧格式导入管线,src/fixtures放块夹具和四份作为黄金文件的 schema-4 旧审查。 - packages/review/src/review-api/blocks/
- 十四种块,一种一个文件——从
sequence、flow_diagram,经database_lens、software_map、code、code_peek、call_stack_diff、trace_quote,到markdown、callout、divider、image、section、tutorial——外加共用的definition.ts、负责注册的index.ts,以及发放身份的ids.ts:这就是允许 agent 拿来画画的整套词汇表。 - packages/review/app/src/software-map/
- 最大的单个功能:104 KB 的
SoftwareMap.tsx、83 KB 的c4-layout-geometry.ts、28 KB 的c4-projection.ts,加上边标签与节点测量、键盘导航、快照与解析数据模块、一个快捷键标签页,以及给没有地图的审查准备的缺席态。 - apps/review-desktop/
- 6,534 个文件、88 MB,绝大部分是 vendored 的 Code OSS 及其
UPSTREAM清单,外面裹着 macOS、Windows、Arch、RPM 与 DEB 的打包,再加一套二十一个录制过的端到端 journey,和一份 16 KB 的已知 bug 文档。 - The side packages
local-vcs(git 与 jj 管道,光一个文件就 60 KB)、trace-core(agent 轨迹采集、钩子、托管与 S3 存储)、trace-protocol、review-protocol(一份 42 KB 的 contracts 文件)、review-share-protocol、json,以及携带 Claude、Codex、Cursor、Pi 接入元数据的agent-plugins。它们各自独立发版,这也正是 diff 查看器diffr能待在另一个仓库里、却仍被这里钉住版本的原因。- Tooling and documents
tools/oxlint/anti-slop是一个自定义 lint 插件,十八条规则外加一条 Effect 专用规则和一个canvas-styles插件,管数组链式调用、链式类型断言、字典类型、unknown 参数与返回值、模块 mock,以及类型断言必须写安全注释。docs/telemetry.md49 KB、docs/privacy.md9 KB;AGENTS.md373 字节、CONTEXT.md234 字节、CLAUDE.md11 字节。七个工作流文件驱动持续集成与发布,最大的一个 38 KB。
取舍,以及它替代了什么
把 Code OSS 整个 vendored 进来,而不是维护一份补丁集 替代 一个对着上游 VS Code 打补丁的分支
README 给了两条理由。编辑已经不是主要工作了——写代码交给了 agent 终端和桌面应用,文本编辑器现在主要用来逐行读 diff;而打补丁对这个项目是错的形状,因为「coding agents have a hard time with patches」。vendored 还顺手丢掉了它所说的、占了约 45% 上游代码的 Copilot 部分,代价是上游的安全与功能改动要手工合进来。
对外发布的编辑 schema 用开放对象,宿主解析器保持严格 替代 把每一种块都摊开写进 agent 看到的 schema
pull request 820 量到旧的发布版 schema 有 25,424 字符,超过整个工具目录的一半,而且为了嵌套块的 section 还得动用
$ref。现在发布的那份与宿主真正强制执行的那份由同一个函数生成,两个操作因此不可能各走各的;接受什么、存什么一点没变;又因为客户端不再校验形状,必须加类型判别符,好让报错点名出问题的那种块,而不是一句笼统的非法输入。不再对 agent 宣传软件地图上传 替代 把软件地图模型留在工具目录里
那一种上传背走了每个 agent 都要加载的 253k 字符工具定义里约 196k。软件地图默认关闭,并且预计会以另一种形式回来,所以面向 agent 的工具现在只列图片与轨迹上传,目录降到约 58k 字符——而宿主仍然接受地图上传,调用方原本能做的事一件没少。
布局引擎推迟到第一次真正布局时才加载 替代 让它随画布一起提前加载
ELK 有 1.4 MB,约等于 2.5 MB 画布包的一半,而 C4 几何模块一被 import 就构造实例——于是没有图的审查也在付这笔钱。在 Chromium 里量到的结果是打开画布从 130 ms 降到 50 ms;真的有图的审查付的还是一样多,只是晚了一点。
钉子移动后只对「文件确实改了」的锚点报警 替代 对每一个保留的源码区间都报警
一次 rebase 到更新的 main 上,把三次审查里的 31 个锚点全报了出来,哪怕 main 一个相关文件都没碰过——这让真正落在链接底下的改动和噪音变得没法区分。收窄之后需要那个「两侧之间改了哪些文件」的 provider,也就是旧脚手架曾经有过的能力;而在两批提交根本无法比较的情况下,它保留了照旧全部上报的行为。
只保留 Chromium 的英文界面翻译 替代 把大约 220 个语言目录全带上
macOS 框架里约 48.8 MB 的
*.lproj,其他平台上是locales/*.pak,而界面本身只有英文。三条排除规则写在同一个本来就已偏离上游的构建文件里,一次覆盖三个平台;唯一可见的后果也被写明了,而不是留给用户去发现:Chromium 自己的字符串,比如原生右键菜单,永远是英文。在 agent 读到的 schema 里,把标识符当键发布,而不是当 UUID 替代 用 UUID 格式声明它们
以
z.uuid()声明时,commandId与leaseId会被发布成 format 加一段 250 字符的 pattern——19 处副本,约占全部工具 schema 文本的五分之一——而宿主只把它们当成键用,一个是收据的、一个是租约的。现在它们和别的标识符一样是非空字符串;上传 id 仍然是 UUID,因为那是 agent 自己编的,宿主还用它们给存下来的图片和轨迹做键。
依据packages/review/src/review-api/blocks/*、packages/review/instructions/*、packages/review/README.md(6,674 字节)、packages/review/src/review-api/README.md(25,467 字节)、packages/review/app/src/software-map/ 的模块清单、apps/review-desktop/scripts/linux/ARCH.md、AGENTS.md、docs/telemetry.md 与 docs/privacy.md、README(6,581 字节,经 GitHub API 完整取回),以及三十个 issue 与 pull request 的正文。
制作过程
6 个阶段- 01
六个星期、一次导入、一次改名,和一条预览通道
仓库建于 2026-08-18,二十六分钟之后的最早一次提交标题是 Import Review Desktop——命名的历史就是从这里开始的。按月拆开,这个分布对一个年轻项目来说也算失衡:八月 56 次,九月 519 次。贡献者列表里有十一个账号,其中一个主导:thesiti92 写了 292 次,接着是 ketan0 的 128 次——116 次署名 Ketan Agrawal,另有 12 次挂在报告单独列出的第二个账号上——Sidharth Menon 55 次、Milan Bhandari 35 次、dependabot 32 次、一个 GitHub Actions 机器人 18 次,还有一个
detail-app[bot]11 次。125 次提交带共同作者尾注,其中 64 条写的是模型名:Claude Opus 5.5 (1M context) 35 次、Claude Opus 5.5 14 次、Claude Fable 5.1 11 次、Claude Opus 5 (1M context) 4 次。同样这六个星期里放着十六个 release 和二十个 tag。release 标题至今留着改名的痕迹:仓库出现当天的review-desktop-v0.0.23、2026-09-26 的 「Review Desktop 0.1.3」、以及 2026-09-28 的 「Whiteboard 0.1.5」——第一个用现在这个名字发布的版本。旁边并排跑着带日期和构建号的预览 tag,从v0.0.34-preview.20260924.56到v0.1.6-preview.20260929.79:这是一条真的在发东西的通道,而不是一条等待中的版本线。 - 02
画布上不是方框和箭头,是十四种有类型的块
块的定义放在
packages/review/src/review-api/blocks/,一种块一个文件,一共十四种:sequence、flow_diagram、database_lens、software_map、code、code_peek、call_stack_diff、trace_quote、markdown、callout、divider、image、section、tutorial。每种旁边都有一份 JSON 夹具放在fixtures/blocks/下,另有一个共用的definition.ts、一个 2,375 字节的index.ts,以及负责发放身份的ids.ts。所以这是一套词汇表固定的文档格式,而不是一块可以随便画的板子。sequence块背后是sequence-steps.ts;软件地图是一个 C4 模型,其上是 28 KB 的c4-projection.ts、83 KB 的c4-layout-geometry.ts、c4-node-measurement.ts与c4-edge-label-geometry.ts,底下是 ELK 布局,渲染则是 104 KB 的SoftwareMap.tsx;database_lens就是实体关系图,带一个 36 KB 的渲染器、一份 4.3 KB 的 schema 和自己的 store。section是唯一会嵌套的那种:pull request 820 记录说,它的 schema 为了让 section 嵌套块不得不动用$ref。负责画的那一半在packages/review/app/src里,diagrams.tsx36 KB、trace-document.tsx36 KB、flow-graph.tsx23 KB、blocks.tsx12 KB——而 README 把整套形状的理由写清楚了:点开一张时序图、一张实体关系图,或一条从 agent 轨迹里摘出的引文,就能直接跳到它底下的代码,快捷键和语言服务都来自那个 vendored 的编辑器。 - 03
锚点是一条关于某次提交的断言,而重新钉钉会把它淹掉
每个指向代码的块都带一个源码锚点,而这个锚点才是这套格式真正吃重的地方。2026-09-30 之前 agent 写的是一个对象:
{file, start: {side, line}, end: {side, line}, pins?}。pull request 821 把它换成了 agent 早就会为 markdown 链接写的那种字符串:head/path#L10-L24、base/path#L7,以及区间跨侧时用 GitHub 的 diff 锚点形式diff/path#L84-R90。宿主把它们解析成和以前一样的选取对象,所以存储和画布都没变,而对象形式对测试、教程和更早的调用方仍然接受。字符串没法携带 pins,于是带锚点的块改用块级的pins,markdown 块本来就是这样。锚点在写入时就会对着这次审查钉住的那几次提交做校验。pull request 822 讲的是这些钉子移动之后会发生什么:重新钉钉会对每一个保留下来的区间报警,哪怕它的文件在新提交上与旧提交逐字节相同,于是一次 rebase 到更新的 main 上,把三次审查里的全部 31 个锚点都报了出来,而那些文件 main 一个都没碰过——真正的改动和噪音读起来一模一样。现在宿主会先问清旧新两次提交之间两侧各自改了哪些文件(通过一个由 local-vcs 支撑的filesChangedBetweenprovider),只对这些文件里的锚点报警;而在新钉子上校验不通过的区间,照旧一律上报。值得注意的是锚点的写入形态换了,而存储、画布与读取这三处都没有变——作者在 pull request 821 里特地写明了这一点,并且把对象形式保留给测试、教程与更早的调用方。 - 04
一个用字符数来衡量的 agent 接口
面向 agent 的那一面是一份工具目录,而它的体积被当成一个设计变量,pull request 里直接用字符数把代价写出来。pull request 819:
session_upload的 map 这一种上传,把整个软件地图模型的 schema 都背了进去,约占每个 agent 都要加载的 253k 字符工具定义里的 196k;因为软件地图默认关闭、并且预计会以另一种形式回来,面向 agent 的工具现在只提供图片与轨迹上传,目录因此降到约 58k 字符,而宿主仍然接受地图上传。pull request 820:session_edit对外发布的 schema 把每一种块都摊开写,还得为嵌套的 section 动用$ref——25,424 字符,超过整个目录的一半——于是 agent 现在看到的 content 是一个「自己声明类型的开放对象」,每种块的字段挪进工具描述里一行一条;宿主仍然解析完整的editSchema,所以接受什么、存什么完全没变,而publishedEditSchema与editSchema由同一个函数生成,两者不可能各走各的。客户端不再校验形状之后,宿主的报错就成了唯一反馈,所以块和插入内容改为按类型判别,报错会点名出问题的那种块。pull request 823:commandId与leaseId原本声明成z.uuid(),zod 会把它发布成 format 加一段 250 字符的 pattern,19 处副本,约占全部工具 schema 文本的五分之一,可宿主只把它们当键用;现在改成z.string().min(1),而上传 id 仍然是 UUID,因为那是 agent 自己编的。pull request 821 把session_lens_edit从 4,501 字符压到 3,077。仍在开放的 pull request 825 走得更远:提议删掉那些读代码的工具——session_diff、session_file、session_source、session_tree、session_commits——理由是 agent 只能从持有这个仓库的机器上够到 Whiteboard,而在那台机器上 git 或 jj 读的是同一批提交。这些工具原本约束的行为,改由packages/review/instructions/下四个指令文件承担:authoring.md3,490 字节、trace-archaeology.md4,551 字节、scratchpad.md3,330 字节、file-lenses.md1,150 字节。 - 05
社区回合:一次包冲突、一个换行符 bug、一份 roadmap
样本里三条外部 bug 报告有两条是打包与渲染故障,两条都在当天得到回应。issue 802,来自 LeFelps:从签名过的 pacman 仓库安装会中断,因为包里带了
/usr/bin/review,而这个路径归archlinux-contrib所有。维护者二十九分钟后回了:「oh just saw you put up a pr nice! its just an alias right now, the main cli moved towhiteboardso feel free to axe it on your machine」——随后 pull request 806 把review和review-preview两个别名一次性从 Arch、RPM 和 DEB 里摘掉,理由是这个名字已经退役、可能在别处也撞车,并补了一句「glad we renamed lol」。Arch 仓库的检查现在会先装archlinux-contrib,再在 Whiteboard 安装和卸载之后核验它那个review脚本的归属与内容。issue 805,来自 mhebert-inkbit,带了一整套复现步骤:在 Windows 上core.autocrlf=true时,工作区文件被拿去和 LF 的 blob 原样比对,于是 Markdown 这类纯文本文件显示成每一行都改了——notes.md200 行里改了 200 行。根因在那个 Rust 写的 diff 查看器里,而它是独立项目——diffr,以@dev.fast/diffr的形式钉进来——0.1.8 通过 Git 的 clean filter 读取工作区文件修掉了它;同一个文件在 0.1.7 上量到 200 行改动,在 0.1.8 上是一行。样本里还有两条更小的外部贡献:issue 796,说混合的看板加 diff 视图里查找很慢、会把界面卡住;以及 KappaShilaff 的 pull request 794,把面板分隔条的抓取区域从 10px 加宽到 26px 而不动布局。同一个星期里项目还发布了 roadmap:pull request 811 加了顶层ROADMAP.md,813 把它链到 README 顶部导航,随后作为 wiki 页面发布,并留下一条评论说明通知已勾掉、并链到了 issue 745。 - 06
把性能写成数字,把「删掉」当成习惯
这个仓库把性能当作需要测量并写下来的东西,pull request 里就带着数字。pull request 799:ELK,也就是
elkjs/lib/elk.bundled.js的 1.4 MB,约占 2.5 MB 画布包的整整一半,而c4-layout-geometry.ts在模块加载时就构造了一个实例,于是每次打开画布都要为一个可能根本不存在的图付钱;新增的src/elk.ts暴露一个loadElk(),只动态导入一次并缓存一个共享实例,在 Chromium 里量到没有流程图或软件地图的审查,每次打开画布从 130 ms 降到 50 ms。pull request 809:在一个带代码窥视的画布上查找,会每敲一个键就把所有窥视搜一遍,而每次窥视搜索都会重算结构化 diff,其中utf16Column每个字符都分配一个TextEncoder——在一次 profile 里,526 ms 的窥视搜索有大约 480 ms 花在这里;现在打字停顿 150 ms 之后才搜窥视,被取代的搜索根本不启动。那条打字 journey 从 5,970 ms、渲染进程阻塞 5,493 ms,降到约 515 ms、阻塞 100 ms。pull request 797:Electron 会带上约 220 个 Chromium 界面翻译目录——macOS 上Electron Framework.framework里 48.8 MB 的*.lproj,其他平台上则是locales/*.pak——而这个界面只有英文,于是往一个本来就存在的构建过滤器里加了三条排除规则。pull request 814:把 diff 侧栏拖过它 250px 的最小宽度之后,它会折成一条 44px 的窄条,上面只留已读圆环、一个放大镜、每个 lens 名字的头五个字母(Uncategorized 读作n/a)和一个文件图标。pull request 808 值得读两遍:约 250 个测试、109 个文件里大约 4.4k 行被当作「变更探测型、同义反复或重复」删掉,只留下针对仓库身份、UI 状态存储、传输边界和 vendor 完整性的源码扫描不变量守卫,上游 Code OSS 的测试一行未动;而 373 字节的AGENTS.md明确要求:碰到这种测试不要顺手更新,要删掉。pull request 800 走的是相反方向:bug 报告里的轨迹附件被整个删掉,因为没有任何一份报告能走到它。
相关档案
全部档案 →第 078 号
OpenChatCut
一个本地优先的视频剪辑器,剪辑方式是跟它说话:内置 agent 与外部 Codex、Claude Code 会话调用的是界面自己在用的同一套剪辑工具,于是每一处改动都落在一条真实的多轨时间线上——是片段、转场、字幕、特效或音频,仍然能拖、能撤销、能导出。工程与素材留在本机,预览与最终渲染都出自 Remotion。
第 084 号
Zeron
一个 Rust 桌面程序,把你在用的编码 agent——Claude Code、Codex、Cursor、Devin、Grok、Hermes、Pi、Antigravity——装进同一个控制台,默认只在本机跑、不需要账号;只有你主动登录,它才会把这些会话同步到你其它的设备上。
第 067 号
Reticle
一个 MCP 服务器加一个只在开发期生效的 SDK:让编码 agent 从应用内部去读、去操作一个正在运行的 web 或桌面应用,然后给出判词和该改的文件与行号,而不是一张截图。