[pull] main from tldraw:main#536
Merged
Merged
Conversation
having fun with the new overlay utils, this adds a design tool like overlay that tells you the dimensions of your shapes https://github.com/user-attachments/assets/932bd100-8229-4e2b-9695-8a261b8a162d ### Change type - [ ] `bugfix` - [ ] `improvement` - [ ] `feature` - [ ] `api` - [x] `other`
…8772) In order to fix two regressions introduced by #8501 (right-click and drag to pan the camera), this PR makes the canvas's `onContextMenu` handler more selective. The new handler was unconditionally calling `preventDefault` on every trusted `contextmenu` event, and Radix's `composeEventHandlers` skips its own handler when `event.defaultPrevented` is true. That broke the two context menu sources where the SDK relies on the browser-native `contextmenu` rather than synthesising one itself: - ctrl+click on macOS — the OS-translated `contextmenu` after a `pointerdown` with `button=0` (closes #8771) - long-press on Android (and other touch devices) — the `contextmenu` Android Chrome dispatches after the OS long-press timeout (closes #8770) Now we only suppress the native `contextmenu` when it follows a real right-click (`button === 2` with no ctrl modifier). That's the only case where our pointer handling has already decided what to do — either we'll dispatch a synthetic `contextmenu` from `onPointerUp` to open the menu at the release position, or we'll have panned and don't want a menu at all. Every other source (ctrl+click, two-finger trackpad tap on macOS reported as `button=2 + ctrlKey=true` by some Chromium builds, long-press on touch) flows through to Radix as before. ### Change type - [x] `bugfix` ### Test plan 1. On macOS, hold ctrl and click the canvas — the context menu opens at the click position. 2. On Android (or any touch device), long-press the canvas — the context menu opens after the long-press. 3. Right-click on empty canvas — the context menu still opens at the release position (existing behaviour). 4. Right-click + drag — the camera still pans without opening a menu (existing behaviour). 5. Two-finger trackpad tap on macOS — the context menu still opens (existing behaviour). - [x] Unit tests - [x] End to end tests ### Release notes - Fix the context menu not opening on ctrl+click on macOS and long-press on Android. ### Code changes | Section | LOC change | | --------- | ---------- | | Core code | +13 / -8 | Made with [Cursor](https://cursor.com) Co-authored-by: Cursor <cursoragent@cursor.com>
In order to clean up the canvas DOM after the OverlayUtil refactor (#8633), this PR drops the now-redundant `tl-overlays` wrapper around the canvas overlay element and folds the `tl-canvas__in-front` wrapper (with its event handlers) into `InFrontOfTheCanvasWrapper` so it isn't rendered when no `InFrontOfTheCanvas` component is registered. Since the OverlayUtil rewrite, `CanvasOverlays` only renders a single `<canvas class=\"tl-canvas-overlays\">`, and the wrapper's only meaningful contribution was the `--tl-layer-canvas-overlays` z-index. That now lives directly on `.tl-canvas-overlays`. The matching `.tl-overlays` and `.tl-overlays__item` rules are removed, along with `DefaultCursor.tsx`, which was the last consumer of `.tl-overlays__item` and has been orphaned (not exported, not rendered) since the EditorComponents `Cursor` slot was removed in #8633. ### Change type - [x] `other` ### Test plan 1. Run \`yarn dev\` and confirm canvas overlays still render and interact normally (selection, brush, snap lines, scribble, collaborator cursors). 2. Confirm an example that registers an \`InFrontOfTheCanvas\` component still renders inside the \`tl-canvas__in-front\` wrapper and receives pointer/touch events. 3. Confirm overlay stacking order is unchanged (overlays sit above shapes and HTML layers). ### Release notes - None. ### Code changes | Section | LOC change | | --------- | ---------- | | Core code | +21 / -98 |
In order to keep the top-level README accurate against the current repo state, this PR adds the `Image pipeline` starter kit (which is already shipped via `npm create tldraw` in `packages/create-tldraw/src/templates.ts`) to the starter kits list, and notes the required Node.js version (`^20.0.0`, per the `engines` field in `package.json`) in the Local development section. ### Change type - [x] `other` ### Test plan - [ ] Unit tests - [ ] End to end tests ### Code changes | Section | LOC change | | ------------- | ---------- | | Documentation | +2 / -1 | Co-authored-by: Claude <noreply@anthropic.com>
In order to let developers set the editor's UI language without workarounds, this PR adds a `locale` prop to the `<Tldraw>` component. Closes #6321. Previously, setting the locale required configuring user preferences in `onMount` (via `useTldrawUser` or `editor.user.updateUserPreferences`), which caused a flash of the wrong language on first render. Now developers can pass it declaratively: ```tsx <Tldraw locale="fr" /> ``` The locale is a top-level prop rather than an option in `TldrawOptions`, keeping options focused on editor behavior configuration. The priority chain for locale resolution is: 1. User's explicit locale preference (set via `editor.user.updateUserPreferences`) — highest 2. `locale` prop on `<Tldraw>` — new 3. `navigator.languages` default — existing behavior ### Change type - [x] `feature` ### Test plan 1. Render `<Tldraw locale="fr" />` — UI should appear in French immediately with no flash of English 2. Render `<Tldraw />` without locale — behavior unchanged, uses `navigator.languages` 3. Set `locale="fr"` then call `editor.user.updateUserPreferences({ locale: 'ja' })` — Japanese should take priority - [x] Unit tests (existing UserPreferencesManager tests pass) ### Release notes - Add `locale` prop to `<Tldraw>` for setting the UI language declaratively via `<Tldraw locale="fr" />`, eliminating the flash of wrong language on mount. ### API changes - Added `TldrawBaseProps.locale: string | undefined` for declarative locale configuration ### Code changes | Section | LOC change | | --------------- | ---------- | | Core code | +15 / -1 | | Automated files | +1 / -0 | --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…8751) In order to reflect the current `sync-cloudflare` template implementation, this PR updates the template README and the sync docs to describe SQLite (via `DurableObjectSqliteSyncWrapper`) as the room-state persistence mechanism. R2 in the template is only used for uploaded assets (images, videos), not for room snapshots. Closes #8560. Note: this is about the self-hosted template — `apps/dotcom/sync-worker/` still writes R2 snapshots as a backup layer, but that's not what these docs describe. ### Change type - [x] `docs` ### Test plan 1. Read `templates/sync-cloudflare/README.md` and confirm the persistence and deployment sections match the template's actual behavior (SQLite for room state, R2 only for assets). 2. Read `apps/docs/content/docs/sync.mdx` Cloudflare-template section and confirm SQLite is described as the room-state storage. ### Release notes - Sync docs: clarify that the Cloudflare template persists room state to durable object SQLite storage; R2 is only used for assets.
In order to exercise the new `OverlayUtil` system end-to-end under animation load, this PR adds a tower-defense mini-game example that uses overlays for everything except the towers themselves. Towers are real geo shapes; the path, enemies, projectiles, range indicators, placement preview, hit explosions, and per-tower upgrade button are all `OverlayUtil` subclasses. Builds on #8633. The example covers most of the OverlayUtil surface: `isActive` reactivity, `getOverlays` driven by `@tldraw/state` atoms ticked from `editor.on('tick')`, `getGeometry` / `getOverlayAtPoint` hit testing, `getCursor` swaps, `onPointerDown` for placement and upgrades, custom zIndex layering, and shape↔overlay reactivity (towers as locked geo shapes feed projectile spawns and upgrade levels via `shape.meta`). ### How to play - Pick a tower from the toolbar (or press `1` / `2` / `3`), then click the canvas to place it. - Triangle = Archer (50g, fast, low damage) - Rectangle = Cannon (120g, slow, high damage) - Ellipse = Magic (80g, medium, AOE) - Hover a placed tower to see its range; click the `+` in its center to upgrade (cost scales 60% per level, max 4 levels). Upgrades change the shape's `fill` / `color` so the level is readable at a glance. - Click an enemy to chip-damage it for free. Enemies that reach the end cost a life. - `Esc` cancels placement, `Space` restarts. ### Overlays added by the example | Util | zIndex | Role | | --- | --- | --- | | `PathOverlayUtil` | 50 | Decorative enemy path | | `TowerRangeOverlayUtil` | 100 | Hover-driven range ring on placed towers | | `PlacementPreviewOverlayUtil` | 150 | Ghost shape + range circle at the cursor while a tower is armed; click-to-place via `onPointerDown` | | `EnemyOverlayUtil` | 200 | Animated enemies with HP bar; hit-test + click-to-damage | | `ExplosionOverlayUtil` | 230 | Fading ring on Magic AOE impact | | `ProjectileOverlayUtil` | 250 | Arrows / rocks / orbs in flight | | `UpgradeButtonOverlayUtil` | 260 | Per-tower upgrade `+` button; click via `onPointerDown` | ### Notes for reviewers - Placement clicks and upgrade clicks route through `OverlayUtil.onPointerDown` — no DOM intercept. Locked-shape clicks correctly fall through to overlays because `select.idle.onPointerDown` checks `getOverlayAtPoint` before the locked-shape skip. - Upgrade and restart paths wrap `editor.updateShape` / `editor.deleteShapes` in `editor.run(..., { ignoreShapeLock: true })` because the runtime silently skips locked shapes otherwise. - Per-tick shape walks (`UpgradeButtonOverlayUtil._candidates`, `TowerRangeOverlayUtil._hoveredTowers`) are memoised with `computed()` so a mousemove storm only re-runs the cheap pointer filter, not the bounds resolution pass. - Sounds are synthesized with the Web Audio API (no asset loading) — fire tones per projectile kind and a small upgrade chime. ### Change type - [x] `feature` ### Test plan 1. `yarn dev` and open `localhost:5420/use-cases/tower-defense`. 2. Place each tower type via the toolbar buttons and via `1` / `2` / `3` shortcuts. 3. Confirm the placement preview shows ghost shape + range while a tower is armed and disappears when you can't afford it. 4. Hover a placed tower → range ring; click the central `+` → tower's color/fill changes per level and the chime plays. 5. Magic projectile impact → fading explosion ring + AOE damage to nearby enemies. 6. Click an enemy → chip damage; hover an enemy → HP readout above its HP bar. 7. Let enemies through → lives drop, game-over state appears, restart via button or `Space` clears all shapes and resets gold/lives/score/wave timer. 8. Toggle dark/light mode → overlay strokes and fills update. - [x] Unit tests - [x] End to end tests ### Release notes - Add a tower-defense example under `examples/use-cases/tower-defense` showcasing the new `OverlayUtil` system.
In order to make the `tldraw-migrate` skill version-agnostic and stop
duplicating migration recipes between the skill and the release notes,
this PR adds explicit migration guide blocks for every breaking change
in the v4.2, v4.3, and `next.mdx` release notes — and refactors the
skill to drive off those blocks instead of carrying its own copies.
Version-specific knowledge belongs next to the breaking change that
introduced it. The skill stays small and stable; the release notes own
the recipes.
### Release notes content
- `apps/docs/content/releases/v4.2.0.mdx` — TipTap v3 section gets a
migration block covering the dual-install diagnostic, default→named
export changes, `TextStyleKit`/`FontFamily` reorganization, and
transaction handler types.
- `apps/docs/content/releases/v4.3.0.mdx` — Custom shape/binding pattern
section gets a migration block covering
`TLGlobalShapePropsMap`/`TLGlobalBindingPropsMap` augmentation, the
rename ripple when shape names collide, `as const` on `static override
type` / `static override shapeType`, and the heterogeneous
`createShapes`/`updateShapes` cast.
- `apps/docs/content/releases/next.mdx` — adds migration blocks for
`ShapeUtil.indicator()` → `getIndicatorPath()`, `<Tldraw>` options
consolidation (`cameraOptions`/`textOptions`/`deepLinks` → `options`
prop, `embeds` → `EmbedShapeUtil.configure`,
`setDefaultEditorAssetUrls`/`setDefaultUiAssetUrls` demoted to
`@internal`), and `useTldrawUser` removal. Several previously-unmarked
breaking changes in the API list are now marked with `💥` and given
inline replacement guidance.
### Skill changes
- `skills/write-release-notes/SKILL.md` — new step requiring a migration
recipe for every `💥` (block for featured sections; inline replacement
for one-line API entries). Verify step now grep-checks that every `💥`
has a recipe.
- `skills/tldraw-migrate/SKILL.md` — major refactor:
- Renames `${CLAUDE_SKILL_DIR}` → `${SKILL_DIR}` and probes common skill
locations so the skill works under non-Claude agent setups.
- Adds support for migrating to a target version other than `latest`.
Pass a dist-tag (`canary`, `next`, `beta`) or a pre-release semver as
the second argument.
- When the target is a pre-release, auto-fetches
`apps/docs/content/releases/next.mdx` from `main` so canary/next deltas
are searchable alongside the stable changelog.
- Step 4 fix patterns are now version-agnostic — they categorize errors
by TS code and route to the correct migration block in the changelog.
Each sub-step ends with a verify step that re-runs typecheck.
- Quality audit now distinguishes typed `as` casts from `as const` and
import-path `as`, and adds a module-augmentation audit (catches the
anti-pattern of re-exposing `@internal` symbols).
- `skills/tldraw-migrate/detect-target.mjs` (new) — resolves the
migration target from the skill arguments. `latest` by default; supports
dist-tags and pre-release semvers.
### Change type
- [x] `other` (skill + docs)
### Test plan
1. Skim each `<details><summary>Migration guide</summary>` block in
`next.mdx`, `v4.2.0.mdx`, and `v4.3.0.mdx` for accuracy.
2. Verify every `💥` has a recipe: `grep -nE '💥'
apps/docs/content/releases/{next,v4.2.0,v4.3.0}.mdx` and confirm each
line has either a nearby migration block or an inline replacement.
3. Run the migrate skill on a v4.1 → 4.6.0-canary project: confirm the
auto-fetch blocks resolve `${SKILL_DIR}` and that
`references/tldraw-next.mdx` is pulled when the target is a pre-release.
4. Run with no second argument and confirm the target resolves to
`latest` and `tldraw-next.mdx` is skipped.
### Release notes
- Add migration guides for every breaking change in the v4.2 and v4.3
release notes.
### Code changes
| Section | LOC change |
| --------------- | ---------- |
| Documentation | +184 / -4 |
| Config/tooling | +195 / -60 |
I went through all of our docs pages to check for things that seemed to be out of date with changes from 4.0 -> now and which will have changed by 5.0. Mainly changing a few examples where methods have changed, but also fixes a few example code blocks that caused errors ### Change type - [ ] `bugfix` - [ ] `improvement` - [ ] `feature` - [ ] `api` - [x] `other` --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#8777) In order to make container acceptance rules apply consistently during pointer drag (not only on paste or create), this PR filters drag targets and drag callback arguments using `canReceiveNewChildrenOfType` when shape utils override it, and fixes the geometric reparent helper to consider child types when picking candidate parents. ### Change type - [x] `bugfix` ### Test plan 1. Override `canReceiveNewChildrenOfType` on a frame-like or container shape to reject a type (e.g. `geo`). 2. Drag a shape of that type over the container; it should not reparent until release, and should stay on the page when dropped. 3. Drag an accepted type; behavior should match prior frame drag-in behavior. 4. Run `yarn test run src/test/frames.test.ts` in `packages/tldraw`. - [x] Unit tests - [ ] End to end tests ### Release notes - Fix drag-and-drop so shapes that override `canReceiveNewChildrenOfType` reject disallowed child types during drag, not only on paste or create. ### Code changes | Area | Description | Additions | Deletions | | --- | --- | ---: | ---: | | `packages/editor` | `getDraggingOverShape` skips targets that reject all dragged types | 9 | 0 | | `packages/editor` | `getDroppedShapesToNewParents` parent prefilter by child types | 11 | 5 | | `packages/tldraw` | `DragAndDropManager` filters shapes passed to drag callbacks | 62 | 23 | | `packages/tldraw` | Frame regression test for rejecting util | 34 | 0 | | **Total** | | **116** | **28** | Made with [Cursor](https://cursor.com) ### API changes - adds `canRemoveChildrenOfType` for frame-like shapes --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com>
In order to keep the tldraw-migrate skill's release-notes view aligned
with what's actually on `main`, this PR switches the changelog source
from `tldraw.dev/llms-releases.txt` to GitHub's
`apps/docs/content/releases/` directory. A few smaller improvements ride
along to make the skill easier to distribute across agents.
Previously the skill `curl`'d the published `llms-releases.txt` from
tldraw.dev, which meant pre-release notes (`next.mdx`) and recently
merged but unpublished release-note edits were invisible to the
migration workflow. The skill now lists `apps/docs/content/releases/`
via the GitHub contents API and fetches each `.mdx` directly from
`raw.githubusercontent.com`. GitHub is the source of truth.
Other changes:
- Pre-release semver suffixes (e.g. `4.7.0-canary.abc`) are now
preserved in `detect-versions.mjs` and compared correctly in the new
fetcher, so a user on a canary gets the right version range and the
upcoming `next.mdx` content.
- The `${SKILL_DIR}` probe gained `.codex/skills/`.
- Removed `disable-model-invocation: true` from the frontmatter so any
agent that respects skill metadata can auto-invoke after copying.
- Added a one-line cross-agent note above the auto-fetch blocks for
agents that don't auto-run Claude Code's `!`-prefix syntax (Cursor,
Codex, plain Claude API, etc.).
- Replaced `filter-changelog.mjs` with `fetch-release-notes.mjs`
(filtering happens at fetch time).
The motivation is partly to support distributing the skill via a
one-shot prompt — "copy `skills/tldraw-migrate` from this repo into your
repo and follow it" — across different coding agents.
### Change type
- [x] `improvement`
### Test plan
1. From a project on tldraw 4.4.0, run `/tldraw-migrate` — should fetch
v4.5.0 notes from GitHub and skip `next.mdx` (stable target).
2. From a project on a pre-release like `4.7.0-canary.abc` with target
`canary`, run `/tldraw-migrate` — should fetch `next.mdx` into
`references/tldraw-next.mdx`.
3. Pin a stable upper bound: `node
skills/tldraw-migrate/fetch-release-notes.mjs 4.0.0 4.2.0` — should
include v4.1.0 and v4.2.0 only.
4. Place the skill folder under each of `.claude/skills/`,
`.agents/skills/`, `.codex/skills/`, `.cursor/skills/`, `skills/` —
auto-fetch blocks resolve `SKILL_DIR` correctly in each case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )