This file provides guidance to AI coding agents working in this repository.
- Use
pnpm, notnpmoryarn, for repo commands. This repo uses pnpm workspaces;yarn …fails at install and is blocked for agents by a hook. - Run commands from the repo root unless a command explicitly says to run from a workspace.
- Never run bare
tsc; usepnpm typecheckfrom the repo root. - Prefer targeted checks first. Avoid repo-wide test or e2e runs unless the change needs them.
- Keep changes scoped to the request and the affected package. Do not refactor unrelated code.
- Respect existing worktree changes. Do not revert user changes unless explicitly asked.
- Prefer editing existing files over creating new files. Do not add new documentation files unless requested.
- Use sentence case for headings, titles, labels, and documentation text.
This is the tldraw monorepo, an infinite canvas SDK for React applications. It is organized with pnpm workspaces (see pnpm-workspace.yaml).
Core packages:
packages/editor- foundational infinite canvas editor with no default shapes, tools, or UIpackages/tldraw- complete SDK with default UI, shapes, tools, and interactionspackages/store- reactive client-side database, persistence, and migrationspackages/tlschema- shape, binding, and record type definitions and validatorspackages/state- reactive signals librarypackages/syncandpackages/sync-core- multiplayer sync packagespackages/utilsandpackages/validate- shared utilities and validation helperspackages/assets- icons, fonts, translations, and bundled assets
Apps and examples:
apps/examples- SDK examples and demos; the main place for example developmentapps/docs- documentation site at tldraw.devapps/dotcom- tldraw.com app and workersapps/vscode- VS Code extensiontemplates- starter templates for supported frameworks
Requires Node >=22.12.0. Enable Corepack before installing dependencies:
npm i -g corepack && pnpm installComing from Yarn (a checkout, worktree or branch from before the pnpm switch):
- If an install stops with "This repo uses pnpm. Run:", run the commands it lists, in order. They clear out Yarn's install (every
node_modules,.yarnandyarn.lock), set up pnpm if needed, and reinstall. - When merging main into an older branch: delete
yarn.lockif it conflicts (git rm yarn.lock). Ifpnpm-lock.yamlconflicts, take main's copy (git checkout origin/main -- pnpm-lock.yaml) and runpnpm installto add the branch's dependency changes. Never hand-edit the lockfile. - Replace
yarn <script>withpnpm <script>, andyarn workspace <pkg> <script>withpnpm --filter <pkg> <script>.
Development:
pnpm dev- start the examples app at localhost:5420pnpm dev-app- start the tldraw.com client apppnpm dev-docs- start the docs sitepnpm dev-vscode- start VS Code extension developmentpnpm dev-template <template name>- run a template
In a fresh git worktree, run pnpm install first since worktrees start without node_modules.
Build:
pnpm build- build all changed packages incrementallypnpm build-package- build SDK packages onlypnpm build-app- build the tldraw.com client apppnpm build-docs- build the docs site
Testing:
pnpm testin a workspace - run tests in watch modepnpm test runin a workspace - run tests oncepnpm test run --grep "pattern"in a workspace - run matching testspnpm exec vitest- run all tests across the repo; slow, avoid unless necessarypnpm e2e- run examples e2e testspnpm e2e-dotcom- run tldraw.com e2e tests
Code quality:
pnpm lint- lint the package or workspacepnpm lint-current- lint changed filespnpm typecheck- type check all packages and refresh assetspnpm format- format the repopnpm format-current- format changed filespnpm api-check- validate public API reports
- For narrow package changes, run the relevant workspace test first, for example
cd packages/tldraw && pnpm test run --grep "SelectTool". - For changes that affect shared types, migrations, editor behavior, or cross-package contracts, run
pnpm typecheckfrom the repo root. - For public API changes, run
pnpm api-checkand include intentional API report updates. - For asset changes, run
pnpm refresh-assetsorpnpm typecheckso generated assets stay current. - For docs changes, run the narrow docs checks or docs build only when the change affects generated content, MDX behavior, or site structure.
- For e2e behavior changes, run the smallest relevant e2e suite and update snapshots only when behavior intentionally changed.
Reactive state:
- State is managed through
@tldraw/statesignals (Atom,Computed, and related primitives). - Editor state is observable and dependency-tracked. Avoid bypassing existing reactive patterns.
Shapes:
- Shape behavior lives in
ShapeUtilclasses. - Shape utils define geometry, rendering, handles, interactions, and SVG/export behavior.
- Add custom shape behavior through the established ShapeUtil patterns rather than one-off editor patches.
Tools:
- Tools are
StateNodestate machines. - Complex tools use child states for pointer, keyboard, tick, and transition behavior.
- Keep interaction logic close to the tool state that owns it.
Bindings:
- Shape relationships use binding records and
BindingUtilclasses. - Arrows and other connected shapes should update through binding utilities, not ad hoc shape mutation.
Managers:
- Editor subsystems live in
packages/editor/src/lib/editor/managers/as classes owned and disposed by theEditor. - A manager that subscribes to events or holds a resource should extend
EditorManagerand register its cleanup so it runs ondispose():addEditorEvent(event, fn)for editor bus events,register(fn)for everything else (store side effects, reactions, DOM listeners, child resources). Useeditor.timersfor timeouts/intervals/frames andeditor.disposablesfor cleanup on the editor itself. - Don't extend
EditorManagerfor managers with no teardown. See theEditorManagerdoc comment for the full decision guide.
Store and schema:
- Store changes should respect migrations, validators, and schema versioning.
- Schema-affecting changes usually need updates in
packages/tlschemaand focused migration tests.
- Use
packages/editorfor core editor primitives, geometry, managers, and UI-free behavior. - Use
packages/tldrawfor default shapes, default tools, UI, and integration tests that need the full SDK. - Use
apps/examplesfor runnable SDK examples and demonstrations. - Use
apps/docs/contentfor documentation articles and release notes. - Use
apps/dotcom/clientfor tldraw.com frontend behavior. - Use
apps/dotcom/*-workerfor Cloudflare worker behavior. - Use
templatesfor starter project changes.
- Unit tests live alongside source files as
*.test.ts. - Integration tests commonly live in
packages/tldraw/src/test/. - E2E tests live in
apps/examples/e2e/andapps/dotcom/client/e2e/. - Test in
packages/tldrawwhen default shapes, tools, bindings, or UI are involved. - Test in
packages/editorfor core editor behavior that should not depend on default shapes or UI. - Prefer comparing whole objects in assertions when that gives a clearer failure than checking fields one by one.
- See
skills/write-unit-tests/andskills/write-e2e-tests/for detailed test patterns.
- Docs live in
apps/docs/content/. - Examples live in
apps/examples/src/examples/. - Example folders use lowercase kebab-case names.
- Example README frontmatter drives the examples site; keep titles and descriptions sentence case.
- Update docs or examples when an API or user-facing behavior changes.
- See
skills/write-docs/,skills/write-example/, andskills/write-release-notes/for task-specific guidance.
- Canonical agent skills live in
skills/. .agents/skillsis a symlink to../skillsfor generic agent compatibility..claude/skillsis a symlink to../skillsfor Claude compatibility. Keepskills/as the source of truth..cursor/skillsis a symlink to../skillsfor Cursor compatibility.- Skill folders use
skill-name/SKILL.mdwith YAML frontmatter containing at leastnameanddescription. - Put reusable scripts, references, and assets inside the relevant skill folder.
- Do not duplicate skill content for different agents; add compatibility pointers or symlinks instead.
- See
skills/skill-creator/before creating or restructuring skills. - User-facing workflow skills include
skills/pr/,skills/issue/,skills/take/,skills/commit-changes/, andskills/clean-copy/.
TypeScript:
- Follow existing file-local style and abstractions.
- Use workspace types and helpers rather than duplicating definitions.
- Keep public API changes deliberate and reflected in API reports.
- Avoid boolean or ambiguous positional options in new APIs when a named object or enum would make call sites clearer.
React and UI:
- Follow existing component patterns in the relevant app or package.
- Keep user-facing text concise and sentence case.
- Avoid broad UI rewrites when a focused component change is enough.
Generated files:
- Do not hand-edit generated assets, API reports, or schemas unless the repo already expects that file to be edited directly.
- Run the owning generator command when generated output needs to change.
Dependencies:
- Keep dependencies workspace-appropriate.
- If changing dependency manifests or lockfiles, make sure the lockfile update is intentional and included.
- Every package a file imports must be declared in the owning workspace's own
package.json. pnpm'shoistedlinker puts everything in the rootnode_modules, so an undeclared import still resolves here but breaks consumers with strict isolation. Thetldraw/no-undeclared-dependencieslint rule enforces this acrosspackages/*,apps/*,internal/*, andtemplates/*. Shared dev tooling that every workspace runs (vitest,tsx,typescript,turbo) may live only in the rootpackage.json: the rootnode_modulesis reachable from every workspace under any linker. Anything a published package's source imports must be declared by that package. Adding a workspace dependency also needs a matchingreferencesentry in that package'stsconfig.json(pnpm check-packages --fix). - Dependency install/build scripts are off by default, which closes the main supply-chain
postinstallcode-execution path. Every package that ships a build script must be listed underallowBuildsinpnpm-workspace.yaml:truefor packages that genuinely need to build (native/napi modules, binary downloaders),falsefor everything else. pnpm fails the install when a package with a build script isn't listed, so a new one shows up at install time; decide whether it needs to run before adding it.
A comment earns its place by saying something the code cannot: why this way, what breaks otherwise, which bug it guards. The litmus: a good comment names a failure mode, not a mechanism.
Scope: these rules apply to comments you write — new code, and lines you are already changing. Do not sweep existing comments while fixing a bug or refactoring; that buries a small change in a large diff. Leave an existing comment alone unless your change makes it inaccurate, you are rewriting the lines it is attached to, or the user asked for a cleanup. If you notice comments worth cleaning up, mention it or do it in a separate PR.
When writing comments:
- Don't restate the code (
/** Get the toolbar */abovegetToolbar()), narrate it (// Delete the shapesaboveeditor.deleteShapes()), add section banners, list call sites, or write@param/@returnsthat only repeat the signature. - State a rationale once where the shared thing lives; don't copy it across sibling call sites.
- Keep comments shorter than the code they explain. Narrative that spans files belongs in a doc (
README.md,SPEC.md,apps/docs/content/) with a short pointer from the code. - Always keep: non-obvious invariants, issue numbers and provenance, constants nobody should tune blindly, diagrams, and enumerated cases the code must not break.
- In
packages/*, doc comments on the@publicsurface become the API reference; density there is expected. Inapps/*andtemplates/*, keep comments sparse.
- Use sentence case for Markdown headings, UI labels, docs titles, PR titles, and issue titles.
- Capitalize proper nouns, acronyms, and code names normally, for example
PostgreSQL,WebSocket, andNodeShapeUtil. - Use direct, concrete language.
- Do not include AI attribution in commits, PR descriptions, issues, docs, release notes, or generated written content.
- Keep commits focused when asked to commit.
- Use semantic PR titles for pull requests:
<type>(<scope>): <description>. - Never add yourself or an AI tool as a co-author.
- See
skills/pr/andskills/issue/for GitHub workflows, andskills/write-pr/andskills/write-issue/for repository content standards.