Skip to content

perf(canvas): improve panning performance in large rooms#5603

Closed
mimecuvalo wants to merge 8 commits intomainfrom
mime/perf-pan-events
Closed

perf(canvas): improve panning performance in large rooms#5603
mimecuvalo wants to merge 8 commits intomainfrom
mime/perf-pan-events

Conversation

@mimecuvalo
Copy link
Copy Markdown
Member

@mimecuvalo mimecuvalo commented Mar 11, 2025

When I was looking at coalesced events, that had me slowdown my CPU more than usual. It made me notice that panning when there was a lot of shapes was chunky. Digging into the flamegraph, I noted that updateHoveredShapeId was a pretty big culprit. This splits off that function into a throttled or debounced version depending on context of what's happening on the canvas.

Before (note FPS in corner):

Kapture.2025-03-11.at.11.20.32.mp4

After (note FPS in corner):

Kapture.2025-03-11.at.11.19.54.mp4

(Mitja, I tagged you since you've done perf stuff a lot but don't review while you're off!)

Change type

  • improvement

Test plan

  1. Create a shape...
  • Unit tests
  • End to end tests

Release notes

  • Improve performance when panning in a large room with a lot of shapes.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
analytics ✅ Ready (Inspect) Visit Preview Jul 11, 2025 5:35pm
examples ✅ Ready (Inspect) Visit Preview Jul 11, 2025 5:35pm
1 Skipped Deployment
Name Status Preview Updated (UTC)
tldraw-docs ⬜️ Ignored (Inspect) Visit Preview Jul 11, 2025 5:35pm

@huppy-bot huppy-bot bot added improvement Product improvement labels Mar 11, 2025
@steveruizok
Copy link
Copy Markdown
Collaborator

Hm, it should be really fast to get a hovered shape, <1 ms. I'd prefer to make that function faster rather than showing the wrong hovered shape during scroll or pan due to a debounce.

On my machine, the function is slow because we're rendering text as part of finding the hovered shape.

image

@steveruizok
Copy link
Copy Markdown
Collaborator

Text perf fixed here: #5658

@mimecuvalo
Copy link
Copy Markdown
Member Author

Hm, it should be really fast to get a hovered shape, <1 ms. I'd prefer to make that function faster rather than showing the wrong hovered shape during scroll or pan due to a debounce.

It's not the getting of the hovered shape, it's the setting of the hovered shape that's the issue when panning.

On my machine, the function is slow because we're rendering text as part of finding the hovered shape.

For sure, this was an issue as well, but updateHoveredShapeId is still a problem here.

Combining testing with the text perf changes in the other PR, this PR still doubles the perf when panning around. So, we should still address the issue raised here too. Happy to show you offline - on my good machine I see an FPS increase from ~25fps to ~55fps

@steveruizok
Copy link
Copy Markdown
Collaborator

Coming to the same conclusions here: updating the hovered shape should be a lower throttle while moving the camera (or based on pointer velocity).

We've improved a bunch of places that were making geometry etc slow... but I still feel like getting hovered shape is too slow.

@huppy-bot
Copy link
Copy Markdown
Contributor

huppy-bot bot commented Jul 10, 2025

Hey! 👋

This PR contains changes to api-report.api.md, which means you've made some API changes. To help reviewers and SDK users understand the impact, please add a "### API changes" section to your PR description.

Here's an example:

### API changes
- Describe what API changes were made
- List any breaking changes
- Note any new or removed parameters

Once you add this section, the check will pass automatically!

@mimecuvalo
Copy link
Copy Markdown
Member Author

cool cool. i rebased this PR so that's up-to-date again. we can iterate on it or take a different tact, just lemme know.

@steveruizok steveruizok changed the title perf: improve being able to pan around in larger rooms perf(canvas): improve panning performance in large rooms Jan 2, 2026
@steveruizok steveruizok closed this Feb 4, 2026
github-merge-queue bot pushed a commit that referenced this pull request Feb 4, 2026
This PR improves performance when panning in large documents by skipping
expensive hover hit-testing while the camera is moving.

Riffing on #5603

### How it works

Hit-testing shapes is expensive in large documents. When panning, we
don't need continuous hover updates—we just need to resume when the
camera stops.

The logic:
1. Camera idle → update hover normally
2. Camera moving + locked → skip entirely (no hit-testing)
3. Camera moving + no current hover → lock immediately
4. Camera moving + same shape → keep current hover
5. Camera moving + different shape → clear hover and lock

This means: when you start panning over a shape, it stays hovered until
your cursor moves off it, then hover clears and we stop hit-testing
until the camera stops.

### Changes

- Added `cameraState: 'idle' | 'moving'` to `TLInstance` (promoted from
private atom)
- Added side effect that triggers `updateHoveredShapeId` when camera
becomes idle
- Added hover-locking logic in `updateHoveredShapeId.ts`

### Change type

- [x] `improvement`

### API changes

- Added `TLInstance.cameraState: 'idle' | 'moving'` - tracks whether the
camera is currently moving or idle

### Test plan

1. Open a document with many shapes
2. Pan around the canvas
3. Verify hover states don't flicker or cause slowdown
4. Verify hover resumes correctly when panning stops

### Release notes

- Improved performance when panning in large documents by skipping hover
updates during camera movement.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes how `cameraState` is stored/migrated on
`TLInstance` and alters hover hit-testing behavior during camera
movement, which could cause hover/selection regressions in edge cases.
> 
> **Overview**
> Improves panning performance by **skipping expensive hover hit-testing
while the camera is moving** and only resuming hover updates once the
camera returns to idle.
> 
> Promotes camera movement state into `TLInstance.cameraState` (with
defaults + migration), updates `Editor.getCameraState()` to read/write
via instance state, and adds a default side effect to run
`updateHoveredShapeId` when `cameraState` transitions to `idle`.
> 
> Reworks `updateHoveredShapeId` to implement a per-editor “hover lock”
during camera movement: keep the current hovered shape if unchanged,
otherwise clear hover and stop hit-testing until the camera stops; adds
tests covering these scenarios.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
af79115. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>
dodo-Riley added a commit to toonsquare/tldraw that referenced this pull request Mar 26, 2026
* chore(sync): disable rate limiting (#7780)

Cloudflare rate limiting is broken. This disables it temporarily by
making `isRateLimited` always return `false`.

Original code is commented out for easy restoration.

### Change type

- [x] `bugfix`

### Test plan

1. Deploy to staging
2. Verify sync connections work without rate limit errors

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Disables rate limiting across sync-worker entry points, which can
increase load and abuse risk until re-enabled. The change is simple and
easy to revert, but impacts production traffic control.
> 
> **Overview**
> **Temporarily disables sync-worker rate limiting.** `isRateLimited` no
longer calls `env.RATE_LIMITER.limit` and instead always returns
`false`, with the original implementation commented out for later
restoration.
> 
> This effectively bypasses rate-limit enforcement in call sites (e.g.,
websocket/session flows and `submitFeedback`) to avoid Cloudflare
rate-limit failures.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
301450699961b239b0df8d6c33ff15934844ae39. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* [HOTFIX] chore(sync): disable rate limiting

This is an automated hotfix for dotcom deployment.

Original PR: #7780
Original Author: @MitjaBezensek

* Add VSCode extension v2.202.0 [skip ci]

* docs(examples): expand and refine keywords in example READMEs (#7786)

Improves the discoverability of SDK examples by expanding and refining
the keywords in each example's README.md frontmatter.

### Change type

- [x] `improvement`

### Test plan

1. Run `yarn dev` and verify the examples app loads
2. Use the search functionality to verify keywords help find relevant
examples

### Release notes

- Improved search keywords across all SDK examples for better
discoverability

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk documentation-only change that affects example search
metadata; main risk is minor YAML/frontmatter formatting issues
impacting parsing or search results.
> 
> **Overview**
> Improves examples discoverability by **expanding and normalizing
`keywords` frontmatter** across many example `README.md` files (often
converting single-line or inconsistent `keyword(s)` fields into richer
`keywords` arrays).
> 
> No runtime logic changes were made; this primarily affects how the
examples app’s sidebar filter matches against `example.keywords`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0bf2bdb3244a8bd1416f0b12a241b6d749c2493f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* docs: add comprehensive SDK documentation and tooling (#7507)

This PR adds comprehensive SDK documentation along with supporting
tooling and infrastructure.

Closes #7505

## What's included

- **Voice and style guide**: Establishes documentation writing standards
- **Claude commands and skills**: AI-assisted workflows for
documentation generation, evaluation, and improvement
- **SDK features documentation**: 52 detailed articles covering all SDK
capabilities organized by topic
- **Changelog documentation**: Release notes for v2.0 through v4.3
- **Updated docs app**: Navigation, structure, and layout updates to
support new documentation sections
- **Documentation tooling**: Scripts for generating documentation
references and changelog automation
- **GitHub Actions workflow**: Automated changelog updates using Claude
Code

The final state of this branch is identical to the `docs` branch - this
is a clean reimplementation with a narrative-quality commit history
suitable for review.

**Original branch**: [docs](https://github.com/tldraw/tldraw/tree/docs)

### Change type

- [x] `improvement`

### Test plan

1. Run `yarn dev-docs` and verify docs site builds successfully
2. Review SDK features section navigation
3. Verify changelog pages render properly

- [ ] Unit tests
- [ ] End to end tests

### Release notes

- Added comprehensive SDK features documentation covering 52 topics
- Added changelog documentation for v2.0 through v4.3
- Added documentation writing style guide and AI-assisted tooling
- Added GitHub Actions workflow for automated changelog updates

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it adds a large amount of new docs content plus
changes docs-site navigation/layout and introduces an automated
release-notes GitHub Action that can create commits/PRs if
misconfigured.
> 
> **Overview**
> Adds new documentation writing infrastructure: shared style guides
(`writing-guide`, `docs-guide`, `blog-guide`, `release-notes-guide`), a
`review-docs` evaluation loop skill, and a new `update-release-notes`
skill with helper scripts.
> 
> Updates the docs site UI and navigation, including wider layouts,
revised category labels, new MDX components (`Feature`, `CheckItem`,
`StarterKitBento`), starter-kit pages with embeds + a dedicated sidebar,
and refactors sidebar link processing into `processSidebarContent`.
> 
> Introduces a structured release-notes system under
`apps/docs/content/releases/` (`next.mdx` plus historical `v2.x` files),
updates the releases landing page, and adds a GitHub Actions workflow
(`update-release-notes.yml`) to run Claude Code to update release notes
and open a PR. Also includes small operational tweaks (Discord link
update, `.gitignore`/Claude command permission adjustments).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
77f85437c7336b771314cd0774c64fdf63c2ba10. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>

* docs(routing): redirect /starter-kits to /starter-kits/overview (#7789)

Now that starter kits content is served from the docs site, this removes
the Framer rewrite and adds a permanent redirect from `/starter-kits` to
`/starter-kits/overview`.

### Change type

- [x] `improvement`

### Test plan

1. Visit tldraw.dev/starter-kits
2. Verify it redirects to tldraw.dev/starter-kits/overview

- [ ] Unit tests
- [ ] End to end tests

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk routing-only change; main risk is unintended redirect
behavior or SEO implications if other `/starter-kits` paths relied on
the previous rewrite.
> 
> **Overview**
> Routes for `starter-kits` are now handled by the docs app:
`/starter-kits` permanently redirects to `/starter-kits/overview`.
> 
> The previous Framer rewrite for `/starter-kits` has been removed so
the path no longer proxies to `tldrawdotdev.framer.website`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4e6f2ea9a10b54bdcbc9a70f787a8df7ab5223ef. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix(docs): correct example embed URLs to use slug instead of full article ID (#7790)

The example embeds on the docs site were generating broken URLs like
`https://examples.tldraw.com/examples/events/derived-view/full` instead
of the correct `https://examples.tldraw.com/derived-view/full`.

The issue was that `article.id` contains a composite key in the format
`sectionId/categoryId/articleId`, but the examples server expects just
the example slug. This PR extracts the slug (last segment) from the
article ID before constructing the embed URL.

### Change type

- [x] `bugfix`

### Test plan

1. Run `yarn dev-docs`
2. Navigate to any example page (e.g., `/examples/events/derived-view`)
3. Verify the embedded iframe loads correctly from
`examples.tldraw.com/derived-view/full`

### Release notes

- Fix example embeds on docs site generating incorrect URLs

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: a small docs-only URL construction change; main risk is
`slug` being undefined if `article.id` is malformed, which would still
produce a bad embed URL.
> 
> **Overview**
> Fixes docs example embeds to generate URLs using only the example slug
(last segment of `article.id`) instead of the full composite
`section/category/article` ID.
> 
> Updates `Example` to extract `slug` from `article.id` and uses it when
building the `Embed` `src`, preventing broken `examples.tldraw.com`
iframe links.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
2b5862549b190fb1f5382dbc5ffe093a41c06f9d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* docs: expand SDK documentation with new feature guides and clarifications (#7791)

This PR significantly expands and improves the tldraw SDK documentation,
adding new dedicated guides for SDK features and revising existing
content for clarity and accuracy.

### New documentation

**SDK feature guides:**
- AI integrations (ai.mdx) - guidance for using tldraw with AI tools
- Arrow, draw, embed, frame, geo, note, and text shape documentation
- Cross-tab sync, cursor chat, cursors, eraser, grid, pen mode
- Highlighting, indicators, instance state, license key, scribble
- Performance optimization guide
- Visibility controls

**Improvements to existing docs:**
- Rewrote and reorganized editor, assets, collaboration, persistence,
shapes, sync, tools, and user-interface pages for clearer structure
- Updated installation and quick-start guides
- Clarified starter kit documentation (branching-chat, chat,
multiplayer, shader, workflow)
- Updated default-shapes.mdx to reference new dedicated shape guides
- Improved internal skill documentation (review-docs, writing-guide)

### Change type

- [x] `docs`

### Test plan

1. Run `yarn dev-docs` and verify new pages render correctly
2. Check navigation includes new SDK features pages
3. Verify internal links work between docs

### Release notes

- Add comprehensive SDK feature documentation including shape guides, AI
integrations, performance tips, and more

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Docs-only changes (plus internal Claude skill docs) with no runtime or
API behavior changes; main risk is broken links or confusing guidance
due to large rewrites/new pages.
> 
> **Overview**
> **Expands the SDK documentation footprint and refactors several core
guides for clarity.** Adds a new `AI integrations` doc and
introduces/updates multiple `sdk-features` pages (including published
guides like `cursor-chat`/`cursors`, plus new draft placeholders such as
`arrow-shape` and `cross-tab-sync`).
> 
> **Rewrites and trims several existing docs pages** (`editor`,
`persistence`, `tools`, `user-interface`, `collaboration`, `assets`,
`shapes`, `sync`) to be more task-focused, reduce long-form duplication,
and point readers to the new `sdk-features` reference articles and
examples.
> 
> **Maintenance/doc process updates:** improves the internal
`.claude/skills/review-docs` workflow by adding a state-file tracker
with fix verification rounds and a “complete and finish” path;
strengthens the writing guide with explicit guidance on removing
trailing gerund phrases; and updates release notes metadata (e.g.,
`status: published`, refreshed `releases/next`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8c61b1277b02ca9e4c3f49abf62895641ad1fcbc. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix(docs): correct starter-kits overview page article ID check (#7792)

The starter-kits overview page was incorrectly showing a broken embed
because the article ID check compared against `'overview'` instead of
the full article key `'starter-kits/starter-kits_ucg/overview'`.

Article IDs in the docs system use the format
`sectionId/categoryId/articleId` (generated by `getArticleKey()`), not
just the filename slug. The overview page's embed was rendering with a
malformed URL like
`https://starter-kits/starter-kits_ucg/overview.templates.tldraw.dev/`.

### Change type

- [x] `bugfix`

### Test plan

1. Run `yarn dev-docs`
2. Navigate to `/starter-kits/overview`
3. Verify no embed is shown at the top of the page

### Release notes

- Fixed starter-kits overview page showing a broken embed

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: a small conditional change in docs rendering logic that only
affects whether the starter-kits embed is shown for the overview
article.
> 
> **Overview**
> Fixes the `starter-kits` docs page conditional to compare against the
full article key (`starter-kits/starter-kits_ucg/overview`) instead of
just `overview`, preventing the `StarterKitEmbed` from rendering (and
generating a broken URL) on the overview page.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8fb1db2f138f77d8e06d68709af87e3057b40784. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* feat(docs): add copy markdown and open with AI buttons to examples (#7771)

Closes #6720

Adds "Copy markdown" and "Open with AI" buttons to the examples
documentation header. This makes it easier for developers to copy
example markdown to use as context in AI tools, or to open examples
directly in Claude or ChatGPT with pre-filled prompts.

### Change type

- [x] `feature`

### Test plan

1. Go to https://localhost:3000/examples/custom-stroke-and-font-sizes
(or any example page)
2. Verify "Copy markdown" and "Open with AI" buttons appear next to the
title
3. Click "Copy markdown" - verify the page's markdown content is copied
to clipboard
4. Click "Open with AI" - verify dropdown shows Claude and ChatGPT
options
5. Click Claude or ChatGPT - verify it opens the AI tool with a
pre-filled prompt about the page

- [ ] Unit tests
- [ ] End to end tests

### Release notes

- Added "Copy markdown" and "Open with AI" buttons to examples
documentation pages

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Updates docs rendering and content generation by adding new `Article`
fields and new llms.txt outputs, which could affect build/runtime if any
article producers/consumers aren’t updated consistently.
Clipboard/analytics usage is client-side but broadly scoped to all docs
headers.
> 
> **Overview**
> Adds a new client-side `CopyMarkdownButton` (with clipboard +
analytics tracking) and wires it into `DocsHeader`, generating a single
markdown payload that concatenates the article body plus embedded code
files (including filenames and fenced language tags).
> 
> Extends docs content/build pipeline by introducing
`Article.componentCodeFilename`, populating it during section
generation, and updating `generateLlmsTxt` to emit `llms-releases.txt`
and include releases in `llms-full.txt`; also adds a new `LLM
documentation` page describing these exports.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5447a933415804d8f34d64a92beb56a7319259e8. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* Agent fairy backport (#7640)

Things I'd like specific eyes on.
- Is the agent app / agent app provider / agent provider . agent
lifecycle management good? No memory leaks?
- Does the prompt part util registry and agent action util registry
architecture make sense?
- Does the agent action schema registry make sense as a way to allow
multiple modes to have actions with the same type? (we want this because
some modes might want different behaviors and schemas for different
action types, ie if a dev wants to add a mode where the message action
also lets the agent decide to send the message to a slack channel, we
still just want that action to be called 'message' in the agent's eyes)
- Is the readme good?

Potential follow on work
- Should we add memory levels and memory transitions from fairies?
- Should we add notifications and waiting from fairies?

---
In order to improve maintainability and extensibility of the agent
template, this PR restructures it around a manager-based architecture
with clear separation of concerns. This is a clean-copy of the [original
branch](https://github.com/tldraw/tldraw/tree/max/agent-fairy-backport)
with a narrative commit history.

## Summary

- **Manager pattern**: Decompose TldrawAgent into focused managers
(chat, context, actions, mode, etc.)
- **Mode system**: Define what agents can see (prompt parts) and do
(actions) per mode
- **Schema separation**: Split schemas (shared) from implementations
(client)
- **Multi-agent support**: TldrawAgentApp coordinates multiple agent
instances
- **Terminology**: Rename "Simple" to "Focused" for shape format

## Architecture

```
TldrawAgentApp (app-level coordinator)
├── AgentAppAgentsManager (multi-agent registry)
├── AgentAppPersistenceManager (IndexedDB persistence)
└── TldrawAgent (per-agent)
    ├── AgentActionManager
    ├── AgentChatManager
    ├── AgentContextManager
    ├── AgentModeManager
    ├── AgentRequestManager
    ├── AgentLintManager
    ├── AgentUserActionTracker
    └── ...
```

## Key changes

1. **FocusedShape terminology**: Rename SimpleShape → FocusedShape to
clarify purpose
2. **Shared resources**: Move models.ts and icons to shared/
3. **Action schema system**: Zod-based schemas with automatic type
derivation
4. **Prompt part definitions**: Type-safe definitions with priority
ordering
5. **Modular system prompt**: Worker builds prompts from sections based
on mode flags
6. **Base manager classes**: Consistent lifecycle (reset, dispose,
disposables)
7. **Mode-based action masking**: Only show available actions to the
model
8. **Canvas linting**: Detect visual problems (text overflow,
overlapping, etc.)
9. **User action tracking**: Record user changes between agent requests

### Change type

- [x] `improvement`

### Test plan

This is a pure refactoring - the final state is identical to the
original branch.

1. Run `yarn typecheck` - should pass
2. Run `yarn dev-template agent` and verify the agent works
3. Compare branches: `git diff max/agent-fairy-backport
max/agent-fairy-backport-clean` should be empty

### Release notes

- Restructured agent template with manager-based architecture for better
modularity
- Added mode system for controlling agent capabilities
- Renamed SimpleShape to FocusedShape for clarity

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk due to large-scale refactor of core agent
lifecycle/prompting/action execution plus new persistence and mode
gating; regressions could break prompting loops, action application, or
state restoration.
> 
> **Overview**
> Refactors the agent template to an app/manager architecture:
introduces `TldrawAgentApp` + `TldrawAgentAppProvider` for agent
lifecycle and localStorage persistence, and decomposes `TldrawAgent`
state into managers (requests, chat, context, mode, actions, lints,
debug, todos, user-action tracking).
> 
> Adds a mode system (`AgentModeDefinitions`/`AgentModeChart`) that
explicitly controls which prompt parts and action types are available
per mode, including mode lifecycle hooks and support for mode-scoped
action implementations via new self-registering registries
(`registerPromptPartUtil`, `registerActionUtil`). Also updates
shape/data terminology and plumbing (`Simple*` �� `Focused*`), expands
helper APIs (offset/rounding helpers), adds canvas lint reporting, and
refreshes the README/documentation to match the new structure and APIs.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c521002389dc0a4de66b9ed2623c97d498c71ec0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* cleanup: rm core-js usage (#7769)

these functions have baseline support now, we don't need this dependency
anymore

fixes https://github.com/tldraw/tldraw/issues/5945

### Change type

- [ ] `bugfix`
- [x] `improvement`
- [ ] `feature`
- [ ] `api`
- [ ] `other`

### Release notes

- cleanup: rm core-js usage

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk cleanup that removes bundled polyfills and related types; the
main risk is runtime regressions in older browsers/environments that
relied on `core-js` for `at`, `flat`, `flatMap`, or `replaceAll`.
> 
> **Overview**
> Removes `core-js` usage from `@tldraw/editor` by dropping the
`core-js` and `@types/core-js` dependencies and deleting the explicit
polyfill imports from `src/index.ts`.
> 
> Updates `yarn.lock` accordingly and trims the dependency list in
`CONTEXT.md`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
48323f4a75316a344c14683259e19e10ba1c5060. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* docs(starter-kits): make bento images clickable and improve copy (#7800)

In order to improve navigation on the starter kits overview page, this
PR makes the images in `StarterKitBento` components clickable, linking
to their respective starter kit detail pages. This allows users to click
directly on the visual preview to learn more.

Additionally:
- Adds explicit "Try the X starter kit" links below several sections for
clearer CTAs
- Updates the "Why build with starter kits" section copy to better
describe the value proposition
- Fixes a typo in the branching image filename (`bramching.png` →
`branching.png`)

### Change type

- [x] `improvement`

### Test plan

1. Navigate to the starter kits overview page
2. Verify each bento image is clickable and links to the correct starter
kit page
3. Verify the "Try the X starter kit" links work correctly

- [ ] Unit tests
- [ ] End to end tests

### Release notes

- Improved starter kits overview page with clickable images and clearer
calls to action

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk docs/UI change: adds a required link prop and updates MDX
content/copy with no backend, auth, or data-handling impact.
> 
> **Overview**
> Improves navigation on the starter kits overview by making
`StarterKitBento` preview images linkable via a new required `href` prop
and wrapping the image in an anchor.
> 
> Updates `starter-kits/overview.mdx` to pass `href` to each bento, adds
clearer “Try the … starter kit” CTAs under several kits, and refreshes
the *Why build with starter kits* copy to better describe the
SDK-focused value proposition.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
717d6aeb9cbdca81f2e16aa35f7702e92f22e923. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* feat(zoom): add quick zoom navigation (#7801)

Adds a "quick zoom" or "eagle eye" feature to the zoom tool for fast
canvas navigation. When in zoom mode, pressing Shift zooms out to 5% and
shows a viewport brush. Move the cursor to select where to zoom, then
release Shift to zoom to that location. Press Escape to cancel.

This is a reimplementation of the `mime/eagle-eye` branch with a clean
commit history for easier review.

Original branch: https://github.com/tldraw/tldraw/tree/mime/eagle-eye

### Change type

- [x] `feature`

### Test plan

1. Open the editor with some shapes
2. Press `z` to enter zoom mode
3. Press Shift to zoom out to 5% ("eagle eye" view)
4. A viewport brush appears at 1/4 screen size
5. Move cursor to reposition where you'll zoom to
6. Release Shift to zoom to the brush location
7. Alternatively, press Escape to cancel and return to original view
8. Verify it works from any tool (draw, select, etc.)

- [x] Unit tests

### API changes

- Added `'action.select-zoom-tool'` to `TLUiTranslationKey` type

### Release notes

- Added quick zoom navigation: press `z` then hold Shift to zoom out and
see the whole canvas, move cursor to pick a location, release to zoom
there.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adds new zoom-tool state transitions and keyboard shortcut handling
(`z`/Shift/Escape) plus camera adjustments, which could regress existing
input/shortcut behavior and tool-return logic. Changes are localized to
the zoom tool/UI and covered by updated tests.
> 
> **Overview**
> Adds a new *quick zoom (“eagle eye”)* interaction to `ZoomTool`: while
in zoom mode, holding Shift zooms out to 5%, shows a movable viewport
brush, and on release zooms back into the brushed area (Escape cancels
back to the prior view/tool).
> 
> Refactors zoom-tool entry/exit to use full tool-state paths for
`onInteractionEnd`, updates the `select-zoom-tool` action/shortcut
wiring (including undo-safe guards), and extends translations/API types
plus the keyboard shortcuts dialog/docs to expose the new
`select-zoom-tool` action and behavior.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
130bc3e98146edadf2bd1b20afc0256fa35e66f3. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Mime Čuvalo <[email protected]>

* fix(menu): add useCanApplySelectionAction hook to disable menu items when not in select tool (#7811)

In order to properly disable menu items when selection-dependent actions
cannot be applied, this PR adds a reusable `useCanApplySelectionAction`
hook that checks whether the user is in the select tool AND has shapes
selected. This corresponds to the `canApplySelectionAction()` check used
by many actions in `actions.tsx`.

Closes #7810

### Changes

Updated menu items to use the new hook:
- `ZoomToSelectionMenuItem` - now uses `useCanApplySelectionAction`
- `CutMenuItem` - now checks `useCanApplySelectionAction` + unlocked
shapes
- `CopyMenuItem` - now uses `useCanApplySelectionAction`
- `DeleteMenuItem` - now checks `useCanApplySelectionAction` + unlocked
shapes

### Change type

- [x] `bugfix`

### Test plan

1. Create some shapes on the canvas
2. Select one or more shapes with the select tool
3. Switch to a different tool (e.g., press 'D' for draw tool)
4. Open menus and verify that selection-dependent items (zoom to
selection, cut, copy, delete) are disabled
5. Switch back to select tool and verify items are enabled again

- [ ] Unit tests
- [ ] End to end tests

### API changes

- Added `useCanApplySelectionAction()` hook that returns true when in
select tool with shapes selected

### Release notes

- Add `useCanApplySelectionAction` hook for checking if selection
actions should be enabled
- Fix menu items (zoom to selection, cut, copy, delete) being enabled
when not in select tool

---------

Co-authored-by: Steve Ruiz <[email protected]>

* add tldraw-y claude verbs (#7807)

Replaces bad claude verbs with good tldraw claude verbs

### Change type

- [ ] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [ ] `api`
- [x] `other`


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk configuration-only change affecting Claude UI spinner
wording; no runtime application logic is modified.
> 
> **Overview**
> Updates `.claude/settings.json` to **replace** the Claude
`spinnerVerbs` list with a new set of tldraw-style verbs (e.g.,
panning/zooming/drawing/etc.), leaving existing permissions/hooks
unchanged.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
645b99ca4b9d1931f8486d49ac0225e77c135d5a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Steve Ruiz <[email protected]>

* docs: add comprehensive handles / indicators documentation (#7817)

This PR adds a new documentation page for handles in tldraw, covering
how to create and customize interactive control points on shapes.

## Overview

The new `handles.mdx` documentation provides a complete guide to working
with handles in tldraw, including:

- **Handle basics**: How to define handles using `getHandles` on
ShapeUtil
- **Handle types**: Explanation of `vertex`, `virtual`, `create`, and
`clone` handle types
- **Drag handling**: How to respond to handle drags with `onHandleDrag`
and lifecycle callbacks
- **Snapping behavior**: Configuration of point and alignment snapping,
angle snapping with Shift, and custom snap geometry via
`getHandleSnapGeometry`
- **Complete example**: A fully functional speech bubble shape with a
draggable tail handle
- **API reference**: How to read handles using `Editor#getShapeHandles`
- **Examples**: Links to example implementations

### Change type

- [x] `docs`

### Test plan

- [x] Documentation review
- [x] Code examples are syntactically correct and follow tldraw patterns
- [x] All referenced APIs and methods exist in the codebase

### Release notes

- Add comprehensive documentation for handles, including creation,
customization, snapping, and lifecycle management

---------

Co-authored-by: Claude <[email protected]>

* perf(bindings): skip unnecessary arrow updates when translating (#7733)

In order to improve performance when translating arrows together with
their bound shapes, this PR adds an early-return check in
`ArrowBindingUtil.onAfterChangeFromShape` to skip unnecessary updates.


https://github.com/user-attachments/assets/e8d3d131-33e5-4b14-b813-12e81fd8a4f6

Previously, `onAfterChangeFromShape` would always call
`arrowDidUpdate()` which performs expensive `reparentArrow()` operations
involving common ancestor lookups, sibling searches, and index
recalculations. When arrows and their bound shapes are moved together,
the bindings remain valid and no reparenting is needed.

This optimization mirrors the existing check in `onAfterChangeToShape`,
now applied to the arrow's own changes.

### Change type

- [x] `improvement`

### Test plan

1. Create multiple shapes and connect them with arrows
2. Select all shapes and arrows together
3. Drag/translate the selection
4. Verify the arrows maintain their bindings correctly
5. Verify performance improvement when moving many connected shapes

### API changes

- Changed `ArrowBindingUtil.onAfterChangeFromShape` to use `shapeBefore`
and `reason` parameters from options (internal implementation change, no
breaking changes to public API)

### Release notes

- Improve performance when translating arrows together with their bound
shapes

Co-authored-by: Claude <[email protected]>

* feat(tldraw): add TldrawUiSelect component (#7566)

This PR adds a new `TldrawUiSelect` component - a select dropdown
primitive wrapping Radix UI's Select, following existing tldraw UI
patterns.

Closes #7560

## Components

- `TldrawUiSelect` - Root component with value/onChange
- `TldrawUiSelectTrigger` - Trigger button
- `TldrawUiSelectValue` - Displays selected value (with optional icon)
- `TldrawUiSelectContent` - Dropdown container
- `TldrawUiSelectItem` - Individual item (styled like checkbox menu
items with check indicator)

## Usage

```tsx
<TldrawUiSelect value={value} onValueChange={setValue}>
  <TldrawUiSelectTrigger>
    <TldrawUiSelectValue placeholder="Select...">{value}</TldrawUiSelectValue>
  </TldrawUiSelectTrigger>
  <TldrawUiSelectContent>
    <TldrawUiSelectItem value="small" label="Small" icon="size-small" />
    <TldrawUiSelectItem value="medium" label="Medium" icon="size-medium" />
    <TldrawUiSelectItem value="large" label="Large" icon="size-large" />
  </TldrawUiSelectContent>
</TldrawUiSelect>
```

## UI primitives example

This PR also adds a new "UI primitives" example that showcases all
tldraw UI components in one place:

- Buttons (normal, primary, danger, disabled, icon buttons)
- Dropdown menus (with items, checkbox items, submenus)
- Select dropdowns (with and without icons)
- Text inputs
- Sliders
- Popovers
- All available icons (displayed in a grid with tooltips)
- Tooltips
- Keyboard shortcut badges

The example uses `OnTheCanvas` to render the UI showcase directly on the
canvas, making it easy to zoom in and inspect components at different
scales (useful for pixel peeping).

## Also included

- Export of `iconTypes` array for enumerating all available icons

### Change type

- [x] `feature`
- [x] `api`

### Test plan

1. Run `yarn dev` and navigate to the "UI primitives" example
2. Test the select dropdowns - verify they open, close, and show the
check indicator on selected items
3. Verify the select items have left-aligned text matching checkbox menu
items
4. Explore the various UI primitives shown in the example

### Release notes

- Added new `TldrawUiSelect` component for building custom select
dropdowns
- Added UI primitives example showcasing all tldraw UI components
- Exported `iconTypes` array for enumerating available icons

### API changes

- Added `TldrawUiSelect`, `TldrawUiSelectTrigger`,
`TldrawUiSelectValue`, `TldrawUiSelectContent`, `TldrawUiSelectItem`
components
- Added `TLUiSelectProps`, `TLUiSelectTriggerProps`,
`TLUiSelectValueProps`, `TLUiSelectContentProps`, `TLUiSelectItemProps`
types
- Added `iconTypes` export (array of all icon type strings)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds a selectable dropdown primitive and a comprehensive example to
showcase UI components.
> 
> - New select primitives: `TldrawUiSelect`, `TldrawUiSelectTrigger`,
`TldrawUiSelectValue`, `TldrawUiSelectContent`, `TldrawUiSelectItem`
with Radix-backed behavior and tldraw styling
> - Exposes `TldrawUiDropdownMenuSubContent` and associated `*Props`
types; updates `index.ts` exports and API report
> - Select styles in `lib/ui.css` (trigger, content, items) and new
component at `lib/ui/components/primitives/TldrawUiSelect.tsx`
> - Example: `apps/examples/src/examples/ui-primitives` with README,
`UiPrimitivesExample.tsx`, and `ui-primitives.css` demonstrating
buttons, dropdowns, select, input, slider, popover, icons, tooltips, kbd
> - Export `iconTypes` array from `tldraw` for enumerating available
icons
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1572a89807a16034d157e498c2c0394930ec6bc5. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>

* fix(actions): add selection check to toggle-lock action (#7815)

The toggle-lock action was missing the `canApplySelectionAction()` guard
that other selection-dependent actions have. This caused the action to
fire even when no shapes were selected, which is inconsistent with
similar actions like group, duplicate, etc.

### Change type

- [x] `bugfix`

### Test plan

1. Open the editor with some shapes on the canvas
2. Click on empty space to deselect all shapes
3. Press `Shift+L` - verify nothing happens
4. Select a shape
5. Press `Shift+L` - verify the shape toggles locked state

- [ ] Unit tests
- [x] End to end tests

### Release notes

- Fixed toggle-lock action (Shift+L) firing when no shapes are selected.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk behavior change: adds early returns so selection-dependent
keyboard actions no longer fire (or emit UI events) when nothing is
selected; main risk is minor UX regression if any workflows relied on
the old no-op event emission.
> 
> **Overview**
> Prevents selection-dependent actions from running when no shapes are
selected by adding `canApplySelectionAction()` guards to `toggle-lock`,
`enlarge-shapes`, and `shrink-shapes`.
> 
> Updates the Playwright keyboard shortcut E2E test to assert these
shortcuts do **not** emit events when nothing is selected, and still
emit the expected events once shapes are selected.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
bccc5282f259e1a1632c271f94c8eb201a106bb7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix(editor): reduce tab width in rich text to 2 spaces (#7796)

In order to reduce the excessive tab indentation in text shapes, this PR
adds a `tab-size: 2` CSS rule to `.tl-rich-text .ProseMirror` so tabs
render at 2 space widths instead of the browser default of 8.

Closes #7703

### Change type

- [x] `bugfix`

### Test plan

1. Run `yarn dev`
2. Create a text shape (press T or select text tool)
3. Type some text and press Tab to insert indentation
4. Verify the tab renders at 2 space widths instead of 8

### Release notes

- Fixed excessive tab indentation in text shapes (now 2 spaces instead
of 8)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk styling-only change that affects how tab characters render in
rich text (including SVG export), with minimal chance of regressions
beyond text layout differences.
> 
> **Overview**
> Reduces rich-text tab indentation by introducing a shared
`--tl-tab-size` CSS variable (default `2`) and applying `tab-size` to
`.tl-rich-text` so Tab characters render at 2-space width instead of the
browser default.
> 
> Ensures exported SVG rich text matches in-editor rendering by adding
`tabSize: var(--tl-tab-size, 2)` to `RichTextSVG`, and updates the
`getSvgString` snapshot accordingly.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4482b9ab0ceede350187b5c248868537c56a6ba6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix: Safari pinch zoom resetting selection to previous shapes (#7777)

fixes https://github.com/tldraw/tldraw/issues/6907

### Change type

- [x] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [ ] `api`
- [ ] `other`

### Release notes

- fix: Safari pinch zoom resetting selection to previous shapes

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches core input/selection state during pinch and pointer events;
small but behavior-sensitive and could cause selection changes across
browsers/devices if assumptions differ.
> 
> **Overview**
> Prevents Safari pinch-zoom from restoring an outdated selection by
**always snapshotting** `pageState.selectedShapeIds` at `pinch_start`
(instead of reusing a previously-stashed value).
> 
> Also **clears** `_selectedShapeIdsAtPointerDown` on `pointer_up` so
subsequent pinch gestures capture fresh selection state rather than
reapplying stale IDs.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
996531da05c45a7d58103e5743e63152af649e8d. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Steve Ruiz <[email protected]>

* fix(dotcom): provide compiled messages to ErrorPage IntlProvider (#7820)

Fixes #7799

The `ErrorPage` component was passing an empty messages object
(`messages={{}}`) to its `IntlProvider`, causing react-intl
`FORMAT_ERROR` when rendering translated strings in `GoBackLink`:

```
[@formatjs/intl Error FORMAT_ERROR] Error formatting default message for: "324a0f3182", rendering default message verbatim
```

This imports the compiled English translations (same file used by
`TlaRootProviders`) and passes them to the `IntlProvider`.

### Change type

- [x] `bugfix`

### Test plan

1. Navigate to an error page (e.g., invalid file URL)
2. Open browser console
3. Verify no FORMAT_ERROR is logged

### Release notes

- Fixed react-intl console errors on error pages

---

Co-authored-by: Kai Gritun <[email protected]>

Co-authored-by: Kai Gritun <[email protected]>
Co-authored-by: Claude Opus 4.5 <[email protected]>

* chore(sync): re-enable rate limiting (#7822)

The Cloudflare rate limiting issue from #7780 has been resolved. This PR
re-enables rate limiting by restoring the original `isRateLimited`
implementation that calls `env.RATE_LIMITER.limit()`.

https://www.cloudflarestatus.com/incidents/dk0d6pjt9vjx

### Change type

- [x] `improvement`

### Test plan

1. Deploy to staging
2. Verify sync connections work normally
3. Verify rate limiting kicks in under heavy load

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Re-enables enforcement of request limits in the sync worker;
misconfigured limiter settings or unexpected limiter behavior could
cause elevated 429s and disrupt sync/feedback flows under load.
> 
> **Overview**
> Re-enables rate limiting in the sync worker by restoring
`isRateLimited` to call `env.RATE_LIMITER.limit({ key })` and returning
`!success`, instead of always allowing requests.
> 
> This changes runtime behavior for all call sites (e.g., websocket
connections/mutations and `submitFeedback`) to actively enforce limits
again.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
17f882a626978fe85314d047634da4fb90db205c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* perf(editor): skip hover updates while panning (#7826)

This PR improves performance when panning in large documents by skipping
expensive hover hit-testing while the camera is moving.

Riffing on https://github.com/tldraw/tldraw/pull/5603

### How it works

Hit-testing shapes is expensive in large documents. When panning, we
don't need continuous hover updates—we just need to resume when the
camera stops.

The logic:
1. Camera idle → update hover normally
2. Camera moving + locked → skip entirely (no hit-testing)
3. Camera moving + no current hover → lock immediately
4. Camera moving + same shape → keep current hover
5. Camera moving + different shape → clear hover and lock

This means: when you start panning over a shape, it stays hovered until
your cursor moves off it, then hover clears and we stop hit-testing
until the camera stops.

### Changes

- Added `cameraState: 'idle' | 'moving'` to `TLInstance` (promoted from
private atom)
- Added side effect that triggers `updateHoveredShapeId` when camera
becomes idle
- Added hover-locking logic in `updateHoveredShapeId.ts`

### Change type

- [x] `improvement`

### API changes

- Added `TLInstance.cameraState: 'idle' | 'moving'` - tracks whether the
camera is currently moving or idle

### Test plan

1. Open a document with many shapes
2. Pan around the canvas
3. Verify hover states don't flicker or cause slowdown
4. Verify hover resumes correctly when panning stops

### Release notes

- Improved performance when panning in large documents by skipping hover
updates during camera movement.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes how `cameraState` is stored/migrated on
`TLInstance` and alters hover hit-testing behavior during camera
movement, which could cause hover/selection regressions in edge cases.
> 
> **Overview**
> Improves panning performance by **skipping expensive hover hit-testing
while the camera is moving** and only resuming hover updates once the
camera returns to idle.
> 
> Promotes camera movement state into `TLInstance.cameraState` (with
defaults + migration), updates `Editor.getCameraState()` to read/write
via instance state, and adds a default side effect to run
`updateHoveredShapeId` when `cameraState` transitions to `idle`.
> 
> Reworks `updateHoveredShapeId` to implement a per-editor “hover lock”
during camera movement: keep the current hovered shape if unchanged,
otherwise clear hover and stop hit-testing until the camera stops; adds
tests covering these scenarios.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
af79115add89f9648a46a66703035def02d57d19. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <[email protected]>

* fix(sync): fix SQLite migration for multiplayer template (#7829)

The multiplayer template's wrangler.toml was using `new_sqlite_classes`
in its v1 migration, which doesn't work for existing deployments that
were originally created with `new_classes` (KV-backed Durable Objects).
Cloudflare skips already-applied migration tags, so the SQLite switch
was silently ignored on redeploy.

This fixes the migrations to be append-only: v1 creates the DO with
`new_classes`, and v2 enables SQLite via `classes_with_sqlite`.

### Change type

- [x] `bugfix`

### Test plan

1. Deploy the multiplayer template to an existing Cloudflare worker that
was previously using KV-backed DOs
2. Verify the v2 migration applies and SQLite storage works

### Release notes

- Fixed SQLite migration for the multiplayer Cloudflare template to work
with existing deployments

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Adjusts Cloudflare Durable Object migration configuration;
misconfiguration could affect deploy/upgrade behavior for existing
rooms, but change is limited to `wrangler.toml` and is straightforward.
> 
> **Overview**
> Fixes `templates/sync-cloudflare/wrangler.toml` Durable Object
migrations to be *append-only* and compatible with existing deployments.
> 
> `v1` now uses `new_classes` (matching original KV-backed creation),
and a new `v2` migration enables SQLite via `classes_with_sqlite` so
redeploys actually apply the storage switch.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1d908f6a5ec4c23d0e1705412fbd46b337282f4a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix(sync): fix Durable Object SQLite migration in multiplayer template (#7832)

In order to fix the multiplayer template's broken Durable Object
migrations (where `classes_with_sqlite` was used as an invalid
Cloudflare migration directive), this PR replaces the old
`TldrawDurableObject` with a new `TldrawDurableObjectSqlite` class using
proper `new_sqlite_classes` migration.

Follows up on #7829 which attempted to fix this by collapsing
migrations, but that approach doesn't work for existing deployments that
already ran v1/v2. Instead, this adds a v3 migration that deletes the
old class and creates a new SQLite-backed one.

Closes #7804

### Change type

- [x] `bugfix`

### Test plan

1. Run `yarn dev-template sync-cloudflare`
2. Verify the Durable Object deploys and rooms work correctly
3. Verify existing deployments can migrate from v1/v2 to v3

### Release notes

- Fix Durable Object SQLite migration in the multiplayer Cloudflare
template

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes Cloudflare Durable Object class names
and migration directives, including deleting the previous class and
introducing a new SQLite-backed class, which can affect existing
deployments during upgrade.
> 
> **Overview**
> Fixes the `sync-cloudflare` multiplayer template’s broken Durable
Object SQLite rollout by switching the durable object binding/export
from `TldrawDurableObject` to a new `TldrawDurableObjectSqlite`
implementation.
> 
> Updates `wrangler.toml` migrations to add a `v3` step that **deletes**
the old class and creates the replacement via Cloudflare’s supported
`new_sqlite_classes` directive (and annotates `v2` as a no-op), and
refreshes generated types in `worker-configuration.d.ts` to point at the
new class.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e1f73f0b8a74ff73bb3d69b02cffcd2a3f7ea218. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix(sync): remove deleted_classes from multiplayer template migration (#7834)

Cloudflare rejects `--delete-class` migrations when the class was
previously referenced by a binding, even if the binding now points to a
different class. This removes `deleted_classes =
["TldrawDurableObject"]` from the v3 migration so the deploy succeeds.
The old class stays around unused.

Follows the same pattern used in the production sync worker when
migrating `TLDR_DOC` from `TLDrawDurableObject` to
`TLFileDurableObject`.

Relates to #7832

### Change type

- [x] `bugfix`

### Test plan

1. Deploy the multiplayer template to Cloudflare
2. Verify the deploy succeeds without the "Cannot apply --delete-class
migration" error

### Release notes

- Fix multiplayer template deployment error caused by deleting a
previously-bound Durable Object class

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk configuration-only change to Cloudflare `wrangler.toml`
migrations; the main impact is on deployment/migration behavior, where
it now avoids a deploy-time rejection.
> 
> **Overview**
> Fixes the multiplayer Cloudflare template’s Durable Object migration
sequence so deployments don’t fail on a `--delete-class` operation.
> 
> Removes the `deleted_classes` directive from the `v3` migration
(leaving the old class undeleted) and clarifies the `v2` comment while
keeping the `v2` migration tag for already-applied deployments.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
78f9c2783d685e91cecd0d7899c96ab54ff008db. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* Revert back to what we had. (#7835)

We'll delete the worker and make it redeploy so it picks up the v1
again.

### Change type

- [x] `bugfix`

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Updates the Durable Object class name and wrangler migrations, which
can affect existing deployments’ object class mapping and stored state
if the migration history differs. Runtime logic is unchanged but
misconfiguration could break room connectivity or data persistence.
> 
> **Overview**
> Reverts the Cloudflare sync template to use a single SQLite-backed
Durable Object class named `TldrawDurableObject` (renaming from
`TldrawDurableObjectSqlite`) and updates exports and generated env
typings accordingly.
> 
> Adjusts `wrangler.toml` to bind `TLDRAW_DURABLE_OBJECT` to the
reverted class and collapses migrations back to a single `v1`
`new_sqlite_classes` entry, removing the prior `v2`/`v3` migration
history.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b9bf9529300e87ae9ce445db5a02fda7b951711a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* docs: add drag and drop SDK documentation (#7830)

Adds new documentation for the shape-to-shape drag and drop system in
tldraw. This covers the `ShapeUtil` callbacks (`onDragShapesIn`,
`onDragShapesOver`, `onDragShapesOut`, `onDropShapesOver`) that shapes
can implement to respond to other shapes being dragged over them.

This is distinct from the existing "External content handling"
documentation which covers content dragged from outside the browser
(files, URLs, etc.).

### Change type

- [x] `docs`

### Test plan

1. Run `yarn dev-docs`
2. Navigate to SDK Features > Drag and drop
3. Verify the article renders correctly with all code examples and
tables

### Release notes

- Add documentation for the shape-to-shape drag and drop system

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: adds a new MDX documentation page only, with no runtime code
or behavior changes.
> 
> **Overview**
> Adds a new `sdk-features/drag-and-drop.mdx` article documenting the
shape-to-shape drag-and-drop system for custom shapes.
> 
> Covers `ShapeUtil` drag/drop callbacks (`onDragShapesIn`,
`onDragShapesOver`, `onDragShapesOut`, `onDropShapesOver`), the
associated info objects, guidance for determining drop targets and
filtering acceptable child types via `canReceiveNewChildrenOfType`, plus
a complete slot-container example and links to related docs/examples.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
55eb4b25cfc50bde0c2d836d29ed848defb7ffe8. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* Remove deprecated downlevelIteration option (#7813)

This option is deprecated in TypeScript 6.0. It doesn't do anything when
`target` is `esnext`, so can be safely removed from this config file.

### Change type

- [ ] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [ ] `api`
- [x] `other`


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk config cleanup: removes a deprecated TypeScript compiler
option that is a no-op when targeting `ESNext`, so it should not affect
builds or runtime behavior.
> 
> **Overview**
> Removes the deprecated `downlevelIteration` setting from
`apps/dotcom/zero-cache/tsconfig.json` (a no-op with `target: ESNext`)
to keep the TypeScript config compatible with newer TypeScript versions.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
ac9912efaa9134d96eafac3b74eff9730d4a4985. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* chore: remove fairy feature (#7809)

Remove fairy feature completely from frontend and backend while
preserving database tables.

Closes #7808

### Change type

- [x] `other`

### Test plan

- [x] Verify `/pricing` route returns 404
- [x] Verify `/fairy-invite/:token` route returns 404
- [x] Verify admin page loads without fairy section
- [x] Verify editor loads without fairy UI
- [x] Verify no build/lint/type errors

### Manual cleanup required (post-merge)

#### GitHub secrets to delete

- [ ] `FAIRY_MODEL`
- [ ] `FAIRY_WORKER`
- [ ] `FAIRY_WORKER_SENTRY_DSN`
- [ ] `DISCORD_FAIRY_PURCHASE_WEBHOOK_URL`
- [ ] `PADDLE_FAIRY_PRICE_ID`
- [ ] Any other `FAIRY_*` prefixed secrets

#### Cloudflare sync-worker env vars

- [ ] `PADDLE_WEBHOOK_SECRET` — keep if used for other Paddle products
- [ ] `PADDLE_ENVIRONMENT` — keep if used for other Paddle products

#### Cloudflare workers to delete

- [ ] Delete fairy worker deployments (production, staging, preview
matching `*-tldraw-fairy`)
- [ ] Delete associated KV namespaces, R2 buckets, Durable Objects
(AgentDurableObject)
- [ ] Delete custom domains matching `*-fairy.tldraw.xyz`

#### Cloudflare KV feature flags

- [ ] Remove `fairies` key from `FEATURE_FLAGS` KV
- [ ] Remove `fairies_purchase` key from `FEATURE_FLAGS` KV

#### DNS/routing

- [ ] Remove any custom domains or routes for `fairy.tldraw.com` or
`/api/fairy/*`

#### Paddle

- [ ] Archive Paddle product if no longer needed
- [ ] Remove webhook endpoints in Paddle dashboard

#### Monitoring

- [ ] Remove fairy worker health check alerts
- [ ] Remove Paddle webhook failure alerts
- [ ] Remove fairy-related error rate alerts

#### Database tables (DO NOT DELETE)

These tables are preserved for data integrity and can be cleaned up
later:
- `user_fairies`
- `file_fairies`
- `fairy_invite`
- `file_fairy_messages`
- `paddle_transactions`

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it changes deployment environment configuration
and removes dependencies/assets; if any remaining code paths still
reference the removed env vars or assets, builds or deploys could fail.
> 
> **Overview**
> **Removes the "fairy" feature surface area from dotcom.** The deploy
workflow no longer passes `FAIRY_*`, fairy Discord webhook, or
`PADDLE_*` fairy-related secrets into the deployment environment.
> 
> On the client, `@tldraw/fairy-shared` and `public/fairy/*` SVG assets
are removed, and i18n extraction/compiled locale bundles are updated to
drop fairy-related message IDs (and other now-unused strings).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b07f83276aa4799bd8da4ac77004e0bbef833a62. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* docs(examples): add conditional culling example (#7847)

Adds a new example demonstrating how to use the `canCull()` method to
conditionally prevent shapes from being culled based on their props.

The example shows two shapes with glow effects that extend beyond their
bounds. One shape has culling disabled (stays visible when panned
off-screen), while the other can be culled (disappears abruptly). This
makes the visual "pop" artifact obvious when comparing the two.


https://github.com/user-attachments/assets/622430ff-2c0c-4d3a-b4cb-4ec72c080ca1

Resolves https://github.com/tldraw/tldraw/issues/7462

### Change type

- [x] `improvement`

### Test plan

1. Run `yarn dev` and navigate to the "Conditional culling" example
under Editor API
2. Pan the canvas horizontally so both shapes move toward the viewport
edge
3. Observe: the shape with "Prevent culling" checked stays visible, the
other disappears at the edge

### Release notes

- Add example showing conditional culling with `canCull()` for shapes
with overflow effects


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Docs/example-only addition with no changes to core library/runtime
behavior; risk is limited to example build/regression in the examples
app.
> 
> **Overview**
> Adds a new **Conditional culling** example that introduces a custom
`glow-shape` with a `preventCulling` prop and overrides
`ShapeUtil.canCull()` to keep the shape rendered even when off-screen.
> 
> The example mounts two glowing shapes and provides an in-shape
checkbox that toggles `preventCulling`, making it easy to compare
“always rendered” vs “culled at viewport edge” behavior; accompanying
README documents the use case and performance tradeoff.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0e2527f673769786514e37844e8f32de96f7663c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* chore: remove CONTEXT.md files and context management scripts (#7852)

In order to reduce maintenance overhead, this PR removes all 33
CONTEXT.md files and the two scripts used to manage them (`yarn context`
and `yarn refresh-context`). Closes #7851.

The CONTEXT.md system required keeping files in sync across the
monorepo, with ~930 lines of script code and a dependency on the Claude
Code CLI. The CLAUDE.md file already provides sufficient guidance for
working with the codebase.

### Change type

- [x] `other`

### Test plan

- [x] Typecheck passes (`yarn typecheck`)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation and developer-tooling removal only; no runtime/product
code paths are affected beyond removing unused scripts from
`package.json`.
> 
> **Overview**
> Removes the monorepo’s AI-focused `CONTEXT.md` documentation system:
deletes the root `CONTEXT.md` plus all package/app `CONTEXT.md` files.
> 
> Eliminates the supporting maintenance tooling by deleting
`internal/scripts/context.ts` and `internal/scripts/refresh-context.ts`,
and drops the `context`/`refresh-context` npm scripts (including any
per-package `context` script usage). Updates `README.md` and `CLAUDE.md`
to remove references to the deleted context workflow and keep agent
guidance centralized in `CLAUDE.md`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e4226e308502a055a27880fedd092818fc12ef6f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* docs: add LOD to performance doc and create a performance example (#7859)

- expand the performance doc
- fix the sorting order
- @steveruizok we might consider adding something to the marketing site
actually, under the Product dropdown
- add an example for performance, came up in a sales call and we didn't
have something we could show off:
<img width="1214" height="1067" alt="Screenshot 2026-02-06 at 18 43 58"
src="https://github.com/user-attachments/assets/60c6e25c-414c-49ad-9dbb-8fa55275923f"
/>


### Change type

- [ ] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [ ] `api`
- [x] `other`

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Docs and example additions with only a small comment/default-threshold
documentation tweak in editor options; no runtime logic changes
apparent.
> 
> **Overview**
> Expands SDK performance documentation to cover **level-of-detail
(LOD)** behavior, including image resolution scaling via
`TLAssetStore.resolve`/`TLAssetContext.steppedScreenScale` and examples
of built-in LOD simplifications.
> 
> Adds a new `ManyShapesExample` that can generate/clear hundreds to
thousands of shapes to showcase culling, batched updates
(`editor.run()`), debounced/efficient zoom behavior, and LOD
transitions. Also fixes docs frontmatter ordering metadata and updates
`TldrawOptions` docs to reference `editor.getEfficientZoomLevel()` and a
500-shape default threshold.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
df78eefa0644c70a50154b04ac3e7aa45e8a895b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Steve Ruiz <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>

* Fix serving of svg assets (#7872)

### Change type

- [x] `bugfix`

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes response security headers for all served uploads, which could
affect client rendering/embedding behavior, but is scoped to asset
download endpoints and reduces XSS risk.
> 
> **Overview**
> Adds defense-in-depth headers when serving user-uploaded assets to
prevent SVG/executable content from running when accessed from the app
origin.
> 
> Cloudflare R2-backed download handlers and the Fastify/Express
template `/uploads/:id` routes now set `Content-Security-Policy:
default-src 'none'` and `X-Content-Type-Options: nosniff` on asset
responses.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3ac8fe3c453fe39c2024a6c202c59639fbc4016e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* [HOTFIX] Fix serving of svg assets (#7873)

This is an automated hotfix PR for dotcom deployment.

**Original PR:** [#7872](https://github.com/tldraw/tldraw/pull/7872)
**Original Title:** Fix serving of svg assets
**Original Author:** @MitjaBezensek

This PR cherry-picks the changes from the original PR to the hotfixes
branch for immediate dotcom deployment.

/cc @MitjaBezensek

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is
generating a summary for commit
c30364f927dbd65041914850faa8aac7a81f3a3e. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Mitja Bezenšek <[email protected]>

* Add VSCode extension v2.203.0 [skip ci]

* license: fix console message colors for warnings/errors (#7850)

oof, i had messed this up in https://github.com/tldraw/tldraw/pull/6844
🤦
i'll have to backport this to the 3.x branch as well, doh


### Change type

- [x] `bugfix`
- [ ] `improvement`
- [ ] `feature`
- [ ] `api`
- [ ] `other`

### Release notes

- license: fix console message colors for warnings/errors

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Only affects verbose console logging styles for license
warnings/errors; no licensing logic or data handling changes.
> 
> **Overview**
> Fixes console output styling for license validation messages so
*warnings* and *errors* render with consistent, type-appropriate colors.
> 
> `outputDelimiter` now accepts the message `type` and uses the same
background color as the message output, and message text is standardized
to white for readability.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6d83c190585f9ea3a2f8d488a6e4ec7bd97f0ea2. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

* fix(dotcom): prevent flash of white background in dark mode (#7842)

In order to prevent the jarring flash of white background when loading
tldraw.com with dark mode enabled, this PR adds early theme detection
that runs before React hydration.

Closes #7542

### Approach

The fix uses a combination of techniques to eliminate the flashbang:

1. **External theme-init.js script** - Loaded in `<head>` before CSS,
this script reads the user's theme preference from localStorage and
immediately sets `data-theme` attribute and inline styles on `<html>`.
Using an external file avoids CSP violations that caused the previous
inline script approach to be reverted.

2. **CSS fallback styles** - New CSS rules in `globals.css` use the
`data-theme` attr…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Product improvement sdk Affects the tldraw sdk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants