首页
/ tldraw monorepo 工程开发指南:面向 AI 编码代理与贡献者的工作流程、架构约定与验证规范

tldraw monorepo 工程开发指南:面向 AI 编码代理与贡献者的工作流程、架构约定与验证规范

2026-09-07 19:35:45作者:魏侃纯Zoe

tldraw 是一个用于构建无限画布(infinite canvas)应用的 React SDK,其代码以 Yarn workspaces 单仓(monorepo)的形式组织。仓库根目录下的 AGENTS.md 是面向在该仓库中工作的 AI 编码代理与人类贡献者的权威开发指南,系统规定了命令工具链、环境搭建、验证工作流、核心架构约定、代码规范与测试布局。本文以该文档为主体,结合根目录 package.json.yarnrc.yml 及核心包源码,逐层还原“在 tldraw 单仓中安全、规范地开发”所需的完整工程知识,帮助读者快速上手开发、规避常见陷阱并理解提交前必须通过的验证关卡。

仓库内的代理指引文件:CLAUDE.md 与 AGENTS.md

在仓库根目录可以看到两类面向 AI 编码代理的指引文件。其中 CLAUDE.md 全文只有一行内容:

@AGENTS.md

这是一个“导入”指令,表示 Claude Code 等代理应直接读取并遵循 AGENTS.md。因此,AGENTS.md 是该仓库唯一的、事实来源式的代理开发指南,也是本文展开的全部骨架。它涵盖了从“用什么包管理器、从哪个目录执行命令”到“编辑器内部子系统如何组织、如何正确编写清理逻辑”的一系列硬性约定。

适用前提:以下所有命令与路径均以当前仓库(tldraw SDK monorepo)为基准,仓库只读,本文仅说明安装、运行与开发验证方式。

仓库总览:Yarn workspaces 单仓结构

tldraw 仓库是一个多 workspace 的 Yarn 4 单仓,根 package.json 中声明的 workspace 覆盖范围包括:packages/*apps/*apps/vscode/*apps/dotcom/*internal/*templates/*

核心包(packages)

SDK 被刻意拆分为“无 UI 的底层核心”与“开箱即用的完整 SDK”两层,各包职责如下:

职责定位
packages/editor 基础无限画布编辑器,不含默认形状、工具与 UI
packages/tldraw 完整 SDK,内置默认 UI、形状、工具与交互
packages/store 响应式客户端数据库、持久化与迁移
packages/tlschema shape、binding、record 的类型定义与校验器
packages/state 响应式信号(signals)库,即 @tldraw/state
packages/syncpackages/sync-core 多人实时同步相关包
packages/utilspackages/validate 通用工具函数与校验辅助
packages/assets 图标、字体、翻译与打包资源

应用、示例与模板

  • apps/examples:SDK 示例与演示工程,是示例开发的主要场所;
  • apps/docs:tldraw.dev 文档站;
  • apps/dotcom:tldraw.com 产品应用及其 Cloudflare Workers;
  • apps/vscode:VS Code 扩展;
  • templates:面向各受支持框架的起始模板。

环境准备与工具链硬性约定

AGENTS.md 开篇即列出一组“核心规则”,它们是整个开发流程的纪律底线:

  • 只用 yarn,不要用 npm 执行仓库命令——仓库基于 Yarn workspaces 与 Yarn 4 构建;
  • 默认从仓库根目录执行命令,除非某条命令明确要求在某 workspace 内运行;
  • 绝不裸跑 tsc,类型检查统一使用根目录的 yarn typecheck
  • 优先做定向检查,避免不必要的全仓测试或 e2e 运行;
  • 改动范围与受影响包保持一致,不要顺手重构无关代码;
  • 尊重已有 worktree 改动,除非被明确要求,不要回滚用户改动;
  • 优先编辑已有文件而非新建文件,除非被要求,不要新增文档文件;
  • 标题、标签、UI 文案与文档一律使用 sentence case(仅句首及专有名词大写)。

从根 package.json 可见运行环境要求:Node >=22.12.0,包管理器固定为 yarn@4.17.1。安装依赖需要先启用 Corepack:

npm i -g corepack && yarn

安装后根 postinstall 钩子会执行 husky install && yarn refresh-assets(见 package.json),用于安装 git 钩子并生成随资源变化的产物文件。

另外值得留意的是仓库的依赖安全策略:.yarnrc.yml 设置了 enableScripts: false,即默认不运行第三方依赖的构建/安装脚本,从源头关闭 postinstall 供应链代码执行通道;确有构建需求的依赖(如 @swc/coreesbuildbetter-sqlite3sharp 等)通过根 package.jsondependenciesMetabuilt: true 显式白名单放行。

常用命令速查

AGENTS.md 依据用途给出了命令族,可在根 package.json 中找到对应脚本定义。

开发(开发命令一律从仓库根目录执行)

命令 作用
yarn dev 启动 examples 应用(AGENTS.md 注明地址为 localhost:5420)
yarn dev-app 启动 tldraw.com 客户端应用
yarn dev-docs 启动文档站
yarn dev-vscode 启动 VS Code 扩展开发
yarn dev-template <模板名> 运行某个模板

yarn dev 为例,根脚本实际是通过 lazyrepo 过滤启动 apps/examplespackages/tldrawapps/bemo-worker 等目标,并先执行各包的 predev 步骤。示例工程的 dev 脚本本身即 vite --host(见 apps/examples/package.json)。

构建

命令 作用
yarn build 增量构建所有有改动的包
yarn build-package 仅构建 SDK 包
yarn build-app 构建 tldraw.com 客户端
yarn build-docs 构建文档站

测试

  • 在某 workspace 内执行 yarn test——watch 模式运行测试;
  • yarn test run——一次性运行测试;
  • yarn test run --grep "pattern"——只运行匹配的测试;
  • yarn vitest——跑遍全仓所有测试(较慢,非必要不用);
  • yarn e2e——examples 的 e2e 测试(根脚本为 lazy e2e --filter='apps/examples');
  • yarn e2e-dotcom——tldraw.com 的 e2e 测试。

代码质量

命令 作用
yarn lint lint 包或 workspace
yarn lint-current 只 lint 改动的文件
yarn typecheck 类型检查全部包,并刷新生成的资源
yarn format / yarn format-current 格式化整个仓库 / 仅改动文件
yarn api-check 校验公共 API 报告与公开类型一致

一个关键的坑:为什么必须从根目录跑 dev

AGENTS.md 特别提醒:根目录的 yarn dev 会先运行各包的 predev,而 predev 会生成构建产物,例如 packages/tldraw/tldraw.css。如果绕过根命令直接执行 workspace 级命令(如 yarn workspace examples.tldraw.com dev),predev 不会执行,导致 import 'tldraw/tldraw.css' 这类引入解析失败。同理,新建的 git worktree 没有 node_modules,必须先 yarn install

lazy.config.ts 的配置注释中可以看到这个设计的底层原因:predev/prebuild 是 css-copy 脚本,会写出被 gitignore 的产物文件(如 tldraw.csscommenting.css);这些文件不是 lazyrepo 追踪的输出,若缓存命中就可能跳过再生成,导致磁盘上缺文件、vite 解析失败,因此这两个脚本被设置为 cache: 'none',每次必跑。

改动提交前的验证工作流

AGENTS.md 给出了按改动类型划分的验证矩阵,是避免 CI 反复失败的核心方法论:

  • 窄范围包改动:先跑相关 workspace 的定向测试,例如 cd packages/tldraw && yarn test run --grep "SelectTool"
  • 涉及共享类型、迁移、编辑器行为或跨包契约的改动:从根目录跑 yarn typecheck
  • 公共 API 改动:跑 yarn api-check,并随改动有意地更新 API 报告(这类文件应通过生成器更新,不可手工乱改);
  • 资源(assets)改动:跑 yarn refresh-assetsyarn typecheck,保证生成的资源文件是最新的;
  • 文档改动:仅当改动影响生成内容、MDX 行为或站点结构时,才运行定向 docs 检查或 docs 构建;
  • e2e 行为改动:运行最小相关 e2e 套件;只有行为是被有意改变的,才更新快照。

这套“先定向、后全量”的策略,与根脚本中 typecheck 串联 refresh-assetspackage.json)、api-check 走 lazyrepo 顶层执行(lazy.config.ts)的实现互相印证。

架构笔记:编辑器五大核心扩展点与子系统生命周期

AGENTS.md 的“Architecture notes”部分直接点明了理解编辑器代码的关键抽象。这些抽象都可以在源码中找到对应实现:

1. 响应式状态:@tldraw/state 信号

状态通过 @tldraw/state 的信号(AtomComputed 及相关原语)管理,编辑器状态可观察且依赖追踪。该包的公开导出位于 packages/state/src/index.ts,包含 atom/isAtomcomputed(随 Computed 原语一起导出)、EffectScheduler/react/reactortransaction/transact 等,底层实现在 packages/state/src/lib

从源码结构看,开发时应避免绕过既有响应式模式直接突变并广播状态,而应遵循该信号体系表达“状态变更”。

2. 形状:ShapeUtil

形状行为集中在 ShapeUtil 类中:几何、渲染、handle、交互、SVG/导出行为都由 ShapeUtil 定义。AGENTS.md 明确要求:添加自定义形状行为应遵循既有的 ShapeUtil 模式,而非用一次性 editor 补丁实现。

3. 工具:StateNode 状态机

工具是 StateNode 状态机。复杂工具用子状态承载 pointer、keyboard、tick 与 transition 行为;交互逻辑应贴近其所属的工具状态,避免散落他处。

4. 绑定:binding 记录与 BindingUtil

形状之间的关联使用 binding 记录与 BindingUtil 类表达。箭头等相连形状应通过 binding utilities 更新,而不是对形状做 ad hoc 突变。

5. 子系统管理器:EditorManager 生命周期约定

AGENTS.md 对“隶属于编辑器的子系统”给出明确的工程约束:这类子系统位于 packages/editor/src/lib/editor/managers/,是作为类存在、由 Editor 拥有并在 dispose() 时释放的。目录实际包含 ClickManager、FocusManager、HistoryManager、InputsManager、SnapManager、TickManager、ThemeManager 等十余个管理器(见 managers 目录)。

凡是会订阅事件或持有资源的管理器,都应继承 EditorManager 并注册其清理逻辑,从而在 dispose() 时自动执行:

  • 编辑器总线事件(tickframechange 等)→ 使用 addEditorEvent(event, fn)
  • 其余一切(store 副作用、reaction、DOM 监听器、子资源)→ 使用 register(fn)
  • 定时器/interval/动画帧 → 用 editor.timers
  • 需要挂在编辑器本身上清理的 → 用 editor.disposables

没有 teardown 需求的管理器则不应继承 EditorManagerEditorManager 的基类实现位于 EditorManager.ts,它内部维护一个 disposables 集合,addEditorEvent 在订阅的同时把对应的 off 注册进集合;文档注释中还透露了这套约定的动机:确保订阅生命周期对称——“谁建立、谁拆除”,以避免像 strict-mode 相机 bug(#8892)那样因忘记清理而引发的故障。若需要有序 teardown(例如先暂停循环再取消它),可覆写 dispose() 并在最后调用 super.dispose(),见 TickManager 的范例。

6. Store 与 Schema:迁移、校验器与版本

Store 的变更必须尊重迁移(migrations)、校验器(validators)与 schema 版本化。涉及 schema 的改动通常需要在 packages/tlschema 中同步更新,并补充聚焦的迁移测试。

代码归属速查:改功能去哪找文件

想要改的东西 去哪个目录
核心编辑器原语、几何、管理器、无 UI 行为 packages/editor
默认形状、默认工具、UI、需要完整 SDK 的集成测试 packages/tldraw
可运行的 SDK 示例与演示 apps/examples
文档文章与 release notes apps/docs/content
tldraw.com 前端行为 apps/dotcom/client
Cloudflare Worker 行为 apps/dotcom/*-worker
起始模板 templates

测试布局与断言风格

tldraw 仓库对测试分层的定义非常清晰,可直接作为“该往哪写测试”的决策依据:

  • 单元测试:与源码同目录,命名为 *.test.ts
  • 集成测试:常见于 packages/tldraw/src/test/,例如该目录下的 Editor.test.tsx、各 Tool 测试等;
  • e2e 测试:位于 apps/examples/e2e/apps/dotcom/client/e2e/
  • 涉及默认形状/工具/binding/UI → 在 packages/tldraw 中测试;
  • 只涉及不依赖默认形状与 UI 的核心编辑器行为 → 在 packages/editor 中测试。

断言语用层面,AGENTS.md 建议:当“整体比较对象”比逐字段断言能给出更清晰的失败信息时,优先整体比较。详细的测试模式约定可参考 write-unit-testswrite-e2e-tests 技能文档。

代码规范要点

TypeScript

  • 遵循文件局部既有风格与抽象,复用 workspace 类型与辅助,而非复制定义;
  • 公共 API 改动须有意为之,并反映在 API 报告中;
  • 新 API 应避免布尔型或含义模糊的位置参数,优先用命名对象或枚举让调用点更清晰。

React 与 UI

  • 遵循相关应用/包内既有组件模式;
  • 用户可见文案保持简洁并采用 sentence case;
  • 能用一个聚焦的组件改动解决,就不要做大范围 UI 重写。

生成文件

  • 不手工编辑生成的资源、API 报告或 schema,除非仓库本来就预期该文件被直接编辑;
  • 需要更新生成输出时,运行其对应的生成器命令。

依赖声明纪律

  • 每个被文件 import 的包,都必须声明在所属 workspace 自己的 package.json 中。原因是 Yarn 的 node-modules linker 会把依赖提升到仓库根,未声明依赖在这里仍能解析成功,但在 pnpm 或 Yarn PnP 下会直接破坏消费者;
  • tldraw/no-undeclared-dependencies lint 规则在 packages/* 全量强制这一约定;
  • 新增 workspace 依赖时,还需在该包的 tsconfig.json 增加对应的 references 项(可用 yarn check-packages --fix 自动完成)。

注释书写原则

AGENTS.md 对注释有一套鲜明标准,核心判据是“注释应说出代码无法表达的东西——为什么这样写、否则会破坏什么、在防御哪个 bug”,也就是注释要能点名一种失败模式,而不是复述机制

  • 不要复述代码(如 /** Get the toolbar */ 写在 getToolbar() 上方)、不要给代码“配音”、不要加章节横幅、不要罗列调用点、不要写只重复签名的 @param/@returns
  • 理由只在共享归属处写一次,不要复制到兄弟调用点;
  • 注释长度应短于其所解释的代码;跨文件的叙述应放进文档(README、SPEC 或 apps/docs/content),代码中只留一句简短指引;
  • 必须保留的内容:不显然的不变量、issue 编号与来源、不应被随意调校的常量、图表,以及“代码绝不能破坏的枚举情形”。

还需注意清理边界:这些规则只适用于你新写的注释和正在修改的行。不要在修 bug 或重构时顺手清理既有注释——那会让一个小改动被淹没在巨大 diff 里。

packages/* 中的 @public 文档注释会成为 API reference 的一部分,因此那里的注释密度是被期待且允许的;而 apps/*templates/* 中应保持注释稀疏。

写作风格与 Git / PR 约定

  • Markdown 标题、UI 标签、文档标题、PR/issue 标题统一使用 sentence case;
  • 专有名词、缩写与代码名按常规大写,例如 PostgreSQLWebSocketNodeShapeUtil
  • 语言直接、具体;
  • 提交、PR 描述、issue、文档、release notes 及生成内容中不得出现 AI 署名

Git 与 PR 方面:提交保持聚焦;PR 标题使用语义化格式 <type>(<scope>): <description>;永远不要把自己或 AI 工具添加为共同作者。可复用的提交规范还能在 commit-changes 中看到完整的允许 type 列表(featfixrefactortestdocschoreperfstylebuildci);GitHub 工作流细节见 prissue 技能,仓库内容标准见 write-prwrite-issue

Skills 技能目录机制

仓库以 skills/ 目录存放“官方”代理技能,并用符号链接兼容不同代理生态:

  • .agents/skills.claude/skills.cursor/skills 均是指向 ../skills 的符号链接;
  • 技能文件夹采用 skill-name/SKILL.md 结构,YAML frontmatter 至少包含 namedescription
  • 可复用的脚本、参考资料与资源放在对应技能文件夹内;
  • 不要为不同代理复制技能内容,改为添加兼容指针或符号链接;
  • 创建或重构技能前,先阅读 skill-creator
  • 面向用户的日常工作流技能包括 skills/pr/skills/issue/skills/take/skills/commit-changes/skills/clean-copy/;工程化技能还包括 skills/write-docs/skills/write-example/skills/write-release-notes/skills/write-unit-tests/skills/write-e2e-tests/skills/tldraw-migrate/ 等,覆盖从文档、示例到迁移与发布的全流程。

小结:AGENTS.md 在工程实践中的真正价值

AGENTS.md 落到日常开发中,可以总结为三条可执行的心智模型:

  1. 统一入口:一切命令以仓库根为锚点,包管理器锁定 yarn,类型检查只信 yarn typecheck——这保证了工具链的可复现与 CI 一致性;
  2. 约定即架构:ShapeUtil 承载形状、StateNode 承载工具、BindingUtil 承载关联、EditorManager 承载子系统清理、tlschema 承载 schema 演化,新增功能的第一选择永远是“复用既有扩展点”,而不是绕过它们打补丁;
  3. 验证分层:窄改动用 --grep 定向测试,跨包契约改动全量 typecheck,公共 API 与资源产物交给生成器与 api-check,把 CI 失败的成本前置到提交之前。

对于任何要在这类大型 React SDK 单仓中工作(无论是 AI 代理还是人类贡献者)的开发者而言,先读透根目录这份 AGENTS.md,就等于拿到了整个仓库“如何被正确修改”的地图。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388