|
| 1 | +--- |
| 2 | +name: tldraw-migrate |
| 3 | +description: Migrate a project to a newer version of the tldraw SDK. Use when upgrading tldraw packages, fixing TypeScript errors after a tldraw upgrade, or when the user mentions tldraw migration. |
| 4 | +argument-hint: '[previous-version]' |
| 5 | +disable-model-invocation: true |
| 6 | +user-invocable: true |
| 7 | +--- |
| 8 | + |
| 9 | +# tldraw migration assistant |
| 10 | + |
| 11 | +You are helping migrate a project to a newer version of the tldraw SDK. Follow this process carefully. |
| 12 | + |
| 13 | +Previous version (auto-detected from git history; pass an explicit version as `/tldraw-migrate <version>` to override): !`node ${CLAUDE_SKILL_DIR}/detect-versions.mjs $ARGUMENTS` |
| 14 | + |
| 15 | +## Resources (auto-fetched on invocation) |
| 16 | + |
| 17 | +!`mkdir -p ${CLAUDE_SKILL_DIR}/references && CHANGELOG=${CLAUDE_SKILL_DIR}/references/tldraw-releases.txt && PREV=$(node ${CLAUDE_SKILL_DIR}/detect-versions.mjs $ARGUMENTS) && curl --fail -sS https://tldraw.dev/llms-releases.txt | node ${CLAUDE_SKILL_DIR}/filter-changelog.mjs "$PREV" > "$CHANGELOG" && echo "Saved changelog (from $PREV) to $CHANGELOG ($(wc -l < "$CHANGELOG") lines)"` |
| 18 | + |
| 19 | +!`DOCS=${CLAUDE_SKILL_DIR}/references/tldraw-full-docs.txt && if [ -s "$DOCS" ]; then echo "Using cached full docs at $DOCS ($(wc -l < "$DOCS") lines) — delete the file to refresh"; else curl --fail -sS https://tldraw.dev/llms-full.txt -o "$DOCS" && echo "Saved full docs to $DOCS ($(wc -l < "$DOCS") lines)"; fi` |
| 20 | + |
| 21 | +- **[Filtered changelog](references/tldraw-releases.txt)** — release notes for versions between the previous version and now. Read this first to understand what changed. |
| 22 | +- **[Full docs](references/tldraw-full-docs.txt)** — complete tldraw SDK docs (~1.5MB). Do NOT read this upfront. Use Grep or Read with line ranges to search for specific topics as needed (e.g., custom shapes, TLTextOptions). |
| 23 | + |
| 24 | +## Step 1: Understand the environment |
| 25 | + |
| 26 | +Before making any changes, scan the project to understand what you're working with. Run these in parallel: |
| 27 | + |
| 28 | +- **Package manager**: Check for lock files (`yarn.lock`, `pnpm-lock.yaml`, `bun.lockb`, `package-lock.json`). Use the corresponding tool throughout. |
| 29 | +- **tldraw packages**: `grep -E "tldraw|@tldraw" package.json` — which packages are installed, and at what versions? Note: not every project uses all tldraw packages. |
| 30 | +- **Import style**: `grep -r "from '@tldraw/" src/ --include="*.ts" --include="*.tsx" -l | head -5` — does the project import from `'tldraw'`, `'@tldraw/editor'`, or both? This affects module augmentation targets. |
| 31 | +- **TypeScript**: Check `package.json` for a `typecheck` or `tsc` script. Also check the TypeScript version — is it a direct dependency or does the project rely on a global install? |
| 32 | +- **Build tool**: Check `package.json` scripts for the build command (vite, next, webpack, esbuild, etc.) |
| 33 | +- **Linter**: Check for eslint/biome config files (`.eslintrc*`, `eslint.config.*`, `biome.json`). A linter may help catch deprecations later. |
| 34 | +- **Monorepo**: Is `package.json` at the working directory root, or is this a nested package? Check for workspaces config. |
| 35 | + |
| 36 | +## Step 2: Upgrade packages |
| 37 | + |
| 38 | +Using the detected package manager, upgrade all tldraw packages that are already in the project's dependencies to the latest version. Don't add new packages the project doesn't already use. |
| 39 | + |
| 40 | +## Step 3: Identify all TypeScript errors |
| 41 | + |
| 42 | +Run the project's typecheck command (from Step 1) and categorize the errors: |
| 43 | + |
| 44 | +1. **Count errors by TS code** (e.g., TS2344, TS2786) to understand the distribution |
| 45 | +2. **List unique files with errors** to understand the scope |
| 46 | +3. **Identify error patterns** — common categories in tldraw upgrades: |
| 47 | + - React types mismatch (TS2786 "cannot be used as JSX component") — usually means `@types/react` version doesn't match what tldraw bundles. To find the bundled version: with npm or yarn classic, check `node_modules/@tldraw/editor/node_modules/@types/react/package.json`; with pnpm or yarn berry, run the package manager's "why" command (e.g. `pnpm why @types/react`, `yarn why @types/react`) to see the resolved range. |
| 48 | + - Custom shape type registration (TS2344 "does not satisfy constraint TLShape/TLBaseBoxShape") — tldraw v4.3+ requires module augmentation of `TLGlobalShapePropsMap` |
| 49 | + - Custom binding type registration — same pattern with `TLGlobalBindingPropsMap` |
| 50 | + - `BaseBoxShapeTool.shapeType` type mismatch (TS2416) — needs `as const` |
| 51 | + - API renames/removals (TS2305, TS2724) — check changelog for specific migrations |
| 52 | + - `createShapes`/`updateShapes` type widening (TS2345) — mapped arrays need `TLShapePartial` or `TLCreateShapePartial` casts |
| 53 | + |
| 54 | +## Step 4: Fix errors in order of impact |
| 55 | + |
| 56 | +Fix in this order (each fix eliminates many downstream errors): |
| 57 | + |
| 58 | +### 4a. Fix React types |
| 59 | + |
| 60 | +If you see TS2786 "bigint not assignable to ReactNode" errors, upgrade `@types/react` and `@types/react-dom` to match tldraw's bundled version. |
| 61 | + |
| 62 | +### 4b. Register custom shapes and bindings |
| 63 | + |
| 64 | +For every `TLBaseShape<'name', Props>` in the codebase, add module augmentation. Use the import style detected in Step 1 as the module target: |
| 65 | + |
| 66 | +```ts |
| 67 | +declare module 'tldraw' { |
| 68 | + interface TLGlobalShapePropsMap { |
| 69 | + 'shape-name': { |
| 70 | + /* props */ |
| 71 | + } |
| 72 | + } |
| 73 | +} |
| 74 | +type MyShape = TLShape<'shape-name'> |
| 75 | +``` |
| 76 | +
|
| 77 | +Same pattern for bindings with `TLGlobalBindingPropsMap` and `TLBinding<'name'>`. |
| 78 | +
|
| 79 | +**IMPORTANT**: If multiple files use the same shape type name with different props, rename them to be unique — they all share the global type registry. |
| 80 | +
|
| 81 | +Add `as const` to all `static override shapeType = '...'` properties. |
| 82 | +
|
| 83 | +### 4c. Fix API renames and removals |
| 84 | +
|
| 85 | +Cross-reference each TS2305/TS2724/TS2339 error with the changelog. Common patterns: |
| 86 | +
|
| 87 | +- Removed exports: find replacement by grepping the tldraw type definitions |
| 88 | +- Renamed properties: check changelog for the new name |
| 89 | +- Changed method signatures: check the current type definitions |
| 90 | +
|
| 91 | +To find tldraw type definitions, check which paths exist — the location varies by version: |
| 92 | +
|
| 93 | +- `node_modules/tldraw/dist-cjs/index.d.ts` |
| 94 | +- `node_modules/tldraw/dist/index.d.ts` |
| 95 | +- `node_modules/@tldraw/editor/dist-cjs/index.d.ts` |
| 96 | +- `node_modules/@tldraw/tlschema/dist-cjs/index.d.ts` |
| 97 | +
|
| 98 | +### 4d. Fix TipTap imports if needed |
| 99 | +
|
| 100 | +If the project uses TipTap (`@tiptap/*` in dependencies or imports), the tldraw upgrade may require upgrading TipTap as well. Starting with tldraw v4.2, tldraw bundles TipTap v3 as a transitive dependency. If the project pins TipTap v2 packages directly, this will cause version conflicts and broken imports. **Upgrade the project's direct `@tiptap/*` dependencies to v3** to match what tldraw expects — leaving them on v2 will not work. |
| 101 | +
|
| 102 | +After upgrading, fix any breaking TipTap v2 → v3 changes: |
| 103 | +
|
| 104 | +- **Default → named exports**: If a default import fails, switch to a named import: `import StarterKit from '@tiptap/starter-kit'` → `import { StarterKit } from '@tiptap/starter-kit'` |
| 105 | +- **Renames**: If a named import fails, check the package's actual exports: `grep 'export' node_modules/@tiptap/<package>/dist/index.js | head` — some extensions were renamed in TipTap v3 (e.g., `TextStyle` → `TextStyleKit`). |
| 106 | +- **Transaction handler types**: If TipTap event handler types break, import the proper types: `import { EditorEvents } from '@tiptap/core'` and type handlers as `(props: EditorEvents['transaction']) => void` rather than inline type annotations. |
| 107 | +
|
| 108 | +### 4e. Fix remaining type errors |
| 109 | +
|
| 110 | +- `createShapes`/`updateShapes` with `.map()`: The proper fix is to add `as const` to the `type` field in the mapped object literal so TypeScript narrows it to the string literal type. If that's not sufficient, use a `satisfies` annotation on the return value. Only as a last resort, cast the result to `TLCreateShapePartial` or `TLShapePartial`. |
| 111 | +- TipTap extension commands not on `ChainedCommands`: use `declare module '@tiptap/core'` augmentation to register custom commands. |
| 112 | +- **General rule**: every `as` cast you add is tech debt. Before adding one, exhaust these alternatives in order: |
| 113 | + 1. `as const` on object literals to narrow string literal types |
| 114 | + 2. `satisfies` annotations to check types without widening |
| 115 | + 3. Proper generic type parameters on the call site |
| 116 | + 4. Module augmentation to teach TypeScript about your types |
| 117 | + 5. Only then, a targeted `as` cast with a comment explaining why it's needed |
| 118 | +
|
| 119 | +## Step 5: Fix deprecations |
| 120 | +
|
| 121 | +After all type errors are resolved, find and fix deprecated API usage. These still compile but should be migrated. |
| 122 | +
|
| 123 | +1. **Find deprecated symbols** — grep the tldraw type definitions for `@deprecated` annotations. Also search the changelog for "deprecated" in `${CLAUDE_SKILL_DIR}/references/tldraw-releases.txt`. |
| 124 | +
|
| 125 | +2. **Check if a linter is configured** — look for eslint, biome, or other linting config in the project. If available, run the linter — it may flag deprecated usage automatically (e.g., eslint's `deprecation/deprecation` rule). If no linter is configured, skip to step 3. |
| 126 | +
|
| 127 | +3. **Search the project source** for each deprecated symbol found in step 1. Replace with the recommended alternative from the `@deprecated` JSDoc comment or changelog. |
| 128 | +
|
| 129 | +## Step 6: Verify |
| 130 | +
|
| 131 | +Run the project's typecheck and build commands (as discovered in Step 1). |
| 132 | +
|
| 133 | +If errors remain, repeat the categorize-and-fix cycle. |
| 134 | +
|
| 135 | +### Post-migration sanity check |
| 136 | +
|
| 137 | +After all errors are resolved, do a quick audit: |
| 138 | +
|
| 139 | +1. **Count `as` casts before vs after**: Run `grep -rn ' as ' --include='*.ts' --include='*.tsx' src/` and compare against the same grep on the pre-migration code (use `git stash` or `git show HEAD:path`). **The migration should add no more than a small handful of new casts** (ideally zero). If you added more than ~5 new `as` casts across the entire migration, go back and fix them — you are almost certainly using the new API incorrectly. |
| 140 | +2. **Review every cast you added**: For each new `as` cast, verify it's truly necessary by checking whether `as const`, `satisfies`, generic type parameters, or module augmentation could replace it. Remove or replace any that have a cleaner alternative. |
| 141 | +3. **Verify no stubs or dead code**: If you stubbed out removed APIs (e.g., replaced a removed function with a no-op), make sure the calling code doesn't depend on the return value. If it does, find the proper replacement in the changelog or docs. |
| 142 | +
|
| 143 | +## Quality checks |
| 144 | +
|
| 145 | +- **Type safety is paramount.** The goal is a migration that is as type-safe as the original code. Do NOT add `as any`, `as unknown`, or broad type casts. Do NOT add `@ts-ignore` or `@ts-expect-error`. These are never acceptable — if you can't make the types work, you don't understand the new API yet. Stop and read the changelog and type definitions before continuing. |
| 146 | +- **Prefer TypeScript's narrowing features over casts.** Use `as const` for literal types, `satisfies` for type-checking without widening, generic parameters for call sites, and module augmentation for extending interfaces. These are the right tools for a migration — `as` casts are not. |
| 147 | +- Use parallel agents for fixing large batches of files with the same pattern. |
| 148 | +- **Don't just make errors go away — understand the new API.** When a method signature changes (e.g., new parameters added, property renamed to a richer type), read the changelog AND the current type definitions to understand _why_ it changed. A fix that compiles but passes hardcoded/dummy values where the new API expects real data is worse than a type error — it silently degrades behavior. For example, if a function gains new required parameters, check what shape props or editor state should feed those parameters rather than passing `0` or `1`. |
| 149 | +- **When unsure about an API pattern**, grep the full docs (`${CLAUDE_SKILL_DIR}/references/tldraw-full-docs.txt`) for usage examples of that specific API. The docs contain code samples that show the canonical way to use each API. |
| 150 | +
|
| 151 | +## Tips |
| 152 | +
|
| 153 | +- Read `${CLAUDE_SKILL_DIR}/references/tldraw-releases.txt` for the filtered changelog — this is the primary source for what changed |
| 154 | +- Grep `${CLAUDE_SKILL_DIR}/references/tldraw-full-docs.txt` when you need docs on a specific API |
| 155 | +- When searching tldraw types, try multiple paths — the dist directory structure varies by version and package manager |
0 commit comments