Skip to content

Agent fairy backport#7640

Merged
max-drake merged 34 commits intomainfrom
max/agent-fairy-backport-clean
Feb 2, 2026
Merged

Agent fairy backport#7640
max-drake merged 34 commits intomainfrom
max/agent-fairy-backport-clean

Conversation

@max-drake
Copy link
Copy Markdown
Contributor

@max-drake max-drake commented Jan 6, 2026

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 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

  • 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

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.

Written by Cursor Bugbot for commit c521002. This will update automatically on new commits. Configure here.

@huppy-bot huppy-bot bot added the improvement Product improvement label Jan 6, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Jan 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 2, 2026 2:57pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 2, 2026 2:57pm
tldraw-docs Ignored Ignored Preview Feb 2, 2026 2:57pm
chat-template Skipped Skipped Feb 2, 2026 2:57pm
tldraw-shader Skipped Skipped Feb 2, 2026 2:57pm
workflow-template Skipped Skipped Feb 2, 2026 2:57pm

Request Review

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Jan 6, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
agent-template c521002 Feb 02 2026, 02:59 PM

@max-drake max-drake marked this pull request as draft January 6, 2026 18:46
@max-drake max-drake changed the title refactor(agent): restructure template with registry pattern and mode system WIP fairy agent backport checkpoint Jan 6, 2026
Rename the shape format terminology from "Simple" to "Focused" throughout
the codebase to better reflect its purpose. Focused shapes represent shapes
that the agent is currently paying attention to, in contrast to "blurry"
shapes in the periphery.

- SimpleColor -> FocusedColor
- SimpleFill -> FocusedFill
- SimpleFontSize -> FocusedFontSize
- SimpleGeoShapeType -> FocusedGeoShapeType
- SimpleShape -> FocusedShape
- convertSimpleShapeToTldrawShape -> convertFocusedShapeToTldrawShape
- convertTldrawShapeToSimpleShape -> convertTldrawShapeToFocusedShape

Also adds text shape improvements including anchor positioning and maxWidth
support for automatic text wrapping.
Move icon components from client/components/icons to shared/icons so they
can be used by both client and worker code. The AgentIcon component now
includes a size parameter for flexibility.
Move the model definitions from the worker directory to shared so they can
be used by both client and worker code. This centralizes the supported model
configuration including:

- claude-sonnet-4-5 (Anthropic, recommended)
- claude-opus-4-5 (Anthropic)
- gemini-3-pro-preview (Google, with thinking)
- gemini-3-flash-preview (Google, fastest)
- gpt-5.2-2025-12-11 (OpenAI)

Adds model definition type with provider, optional thinking override, and
helper functions for validation.
Update the shared type definitions to support the new manager-based
architecture:

- AgentAction: Now derived from schema registry, add Streaming wrapper type
- AgentCanvasLint: New type for canvas linting system
- AgentInput: Add contextItems support
- AgentPrompt: Simplify to BaseAgentPrompt with prompt parts
- AgentRequest: Add source tracking (user/self/other-agent)
- ChatHistoryItem: Add continuation type for chained requests
- ContextItem: Support shape, shapes, area, and point context types
- PromptPart: New architecture with definitions and type derivation
- TodoItem: Add unique ids with todo: prefix
- ids-schema: Add TodoIdSchema for todo item validation

The PromptPart system now uses a definition-based approach where types
are automatically derived from exported definitions, eliminating manual
type maintenance.
Introduce a new schema-based action system that separates action schemas
from action implementations:

AgentActionSchemas.ts:
  - Define all action schemas using Zod with .meta() for descriptions
  - Actions include: create, update, delete, move, place, resize, rotate,
    align, distribute, stack, pen, message, think, review, etc.
  - Each schema has a title and description for documentation

AgentActionSchemaRegistry.ts:
  - Registry pattern for looking up schemas by type
  - Automatic registration from exported schemas
  - Helper functions: getActionSchema, getAllActionSchemas

buildResponseSchema.ts:
  - Build JSON Schema for model responses based on available actions
  - Filters actions based on mode to enable action masking
  - Outputs schema compatible with LLM structured output

This separation allows the schemas to be shared between client and worker
while keeping implementation details client-side only.
Add PromptPartDefinitions.ts which contains pure transformation functions
for converting prompt part data into messages and content. This replaces
the old shared/parts/* files.

Part definitions include:
- BlurryShapesPart: Shapes the agent can see but not clearly
- CanvasLintsPart: Visual problems detected on canvas
- ChatHistoryPart: Conversation history as messages
- ContextItemsPart: User-specified shapes/areas of focus
- DataPart: API data from previous actions
- MessagesPart: User messages to the agent
- ModelNamePart: Which model to use
- PeripheralShapesPart: Shapes in peripheral vision
- ScreenshotPart: Current canvas view
- SelectedShapesPart: User's selected shapes
- TimePart: Current time
- TodoListPart: Agent's todo list
- UserActionHistoryPart: User's recent changes
- UserViewportBoundsPart: User's current view
- AgentViewportBoundsPart: Agent's current view
- ModePart: Agent mode metadata (for action masking)
- DebugPart: Debug flags for logging

Each definition has a priority that determines ordering in the prompt.
The type system automatically derives the PromptPart union type from
exported definitions - no manual type maintenance required.
Refactor the worker to use a modular system prompt architecture with
sections that adapt based on available actions and parts:

getSystemPromptFlags.ts:
  - Compute flags from action/part types to conditionally include rules
  - Flags: hasShapes, hasLayout, hasPen, hasChat, etc.

sections/intro-section.ts:
  - Agent identity and canvas coordinate system
  - Conditional sections for shapes, layout, colors, fonts

sections/rules-section.ts:
  - Behavioral rules that adapt to available capabilities
  - Rules for: creating shapes, arranging, labels, arrows, etc.

sections/flagged.ts:
  - Helper for conditionally including flagged content

buildSystemPrompt.ts:
  - Compose sections and optionally append JSON schema
  - Uses mode part to determine available actions

buildMessages.ts:
  - Build messages from prompt parts with priority ordering
  - Supports text and image content

AgentService.ts:
  - Updated to use shared models.ts
  - Configure thinking budgets per provider
  - Anthropic cache breakpoints for system prompt
  - Debug logging support via debug part flags

Also removes buildResponseSchema.ts from worker (moved to shared).
Add base classes for the manager pattern that will be used to decompose
the TldrawAgent into focused, single-responsibility managers:

BaseAgentManager:
  - Base class for managers that belong to a TldrawAgent instance
  - Provides disposables set for cleanup
  - Abstract reset() method for state reset

BaseAgentAppManager:
  - Base class for managers that belong to TldrawAgentApp (app-level)
  - Same pattern as BaseAgentManager but for app-level concerns

This pattern allows:
- Clear separation of concerns (chat, context, requests, etc.)
- Consistent lifecycle management (reset, dispose)
- Automatic resource cleanup via disposables set
Add managers that handle specific agent concerns, decomposed from
TldrawAgent for better organization and testability:

AgentChatManager:
  - Manages chat history (prompts, actions, continuations)
  - Push, clear, and history retrieval

AgentChatOriginManager:
  - Tracks the origin point of the agent's chat
  - Used for viewport management

AgentContextManager:
  - Manages context items (shapes, areas, points)
  - Items that focus the agent's attention

AgentDebugManager:
  - Debug flags (logSystemPrompt, logMessages)
  - Logging of completed actions

AgentModelNameManager:
  - Which LLM model to use
  - Defaults to claude-sonnet-4-5

AgentRequestManager:
  - Active and scheduled request tracking
  - Request construction from input
  - Cancel functionality
  - isGenerating state

AgentTodoManager:
  - Agent's personal todo list
  - Add, update, complete, clear operations

Each manager extends BaseAgentManager for consistent lifecycle.
Introduce a mode system that controls what the agent can see and do:

AgentModeDefinitions.ts:
  - Define mode types with available parts and actions
  - Active modes can take actions, inactive modes cannot
  - "idling" mode: agent is inactive, waiting
  - "working" mode: agent can see shapes, take actions, etc.
  - Lists all available prompt parts and actions for working mode

AgentModeChart.ts:
  - State machine for mode transitions
  - onPromptStart: transition to working mode
  - onPromptEnd: transition to idling mode
  - onPromptCancel: handle interruptions
  - Extensible for complex mode transitions

AgentModeManager.ts:
  - Manage current mode state
  - Mode transitions and lookups
  - Access to mode definitions and nodes

This enables action masking - the model only sees actions it can take
in the current mode, reducing confusion and errors.
Move action implementations from shared to client and introduce the
AgentActionUtil pattern with AgentActionManager:

AgentActionUtil.ts:
  - Base class for action utilities
  - Type field links to schema type
  - sanitizeAction: validate and transform action before execution
  - act: execute the action on the editor
  - Optional promise for async follow-up work

AgentActionManager.ts:
  - Registry of action utils by type
  - Look up and execute actions
  - Track action history for chat

Individual action utils (client/actions/):
  - CreateActionUtil: Create shapes from FocusedShape
  - UpdateActionUtil: Update existing shapes
  - DeleteActionUtil: Delete shapes
  - MoveActionUtil: Move shapes with anchor positioning
  - PlaceActionUtil: Position shapes relative to others
  - ResizeActionUtil: Scale shapes from origin
  - RotateActionUtil: Rotate shapes around point
  - AlignActionUtil: Align shapes on axis
  - DistributeActionUtil: Distribute shapes evenly
  - StackActionUtil: Stack shapes with gap
  - PenActionUtil: Freeform drawing
  - LabelActionUtil: Update shape text
  - BringToFrontActionUtil, SendToBackActionUtil: Z-ordering
  - ClearActionUtil: Delete all shapes
  - MessageActionUtil, ThinkActionUtil: Communication
  - ReviewActionUtil, AddDetailActionUtil: Planning
  - SetMyViewActionUtil: Viewport navigation
  - UpsertTodoListItemActionUtil: Todo management
  - CountShapesActionUtil: Shape counting
  - CountryInfoActionUtil, RandomWikipediaArticleActionUtil: External APIs
  - UnknownActionUtil: Catch-all for unrecognized actions

This moves implementation details to client while schemas remain shared.
Add PromptPartUtil classes that gather data to build prompt parts.
These complement the shared PromptPartDefinitions by handling the
client-side data collection:

PromptPartUtil.ts:
  - Base class for prompt part utils
  - getPart(request, helpers): gather data and return part
  - Registry pattern for type-safe lookup

Individual part utils:
  - BlurryShapesPartUtil: Get shapes in agent's view
  - PeripheralShapesPartUtil: Cluster shapes outside view
  - ScreenshotPartUtil: Capture canvas screenshot
  - SelectedShapesPartUtil: Get user's selection
  - ChatHistoryPartUtil: Get chat history
  - ContextItemsPartUtil: Get context items
  - DataPartUtil: Get data from previous actions
  - MessagesPartUtil: Extract messages from request
  - UserViewportBoundsPartUtil: Get user's viewport
  - AgentViewportBoundsPartUtil: Get agent's viewport
  - TodoListPartUtil: Get agent's todos
  - UserActionHistoryPartUtil: Get user's recent changes
  - TimePartUtil: Get current time
  - ModePartUtil: Get mode metadata
  - ModelNamePartUtil: Get model selection
  - DebugPartUtil: Get debug flags
  - CanvasLintsPartUtil: Get canvas lint results

The split between definitions (shared) and utils (client) allows:
- Schemas shared between client and worker
- Client-specific data gathering logic stays client-side
Add two specialized managers for tracking user changes and canvas quality:

AgentUserActionTracker:
  - Track user changes to the canvas (not agent changes)
  - Records added, removed, and updated shapes
  - Uses editor store listener to capture changes
  - Filters out agent actions using isActingOnEditor flag
  - Provides history of user modifications to inform agent

AgentLintManager:
  - Detect potential visual problems on the canvas
  - Lint types:
    - growY-on-shape: Text overflow causing shape growth
    - overlapping-text: Shapes with text overlapping each other
    - friendless-arrow: Arrows not connected to shapes
  - Track shapes created during agent actions
  - Run lints and report issues
  - Helps agent self-correct visual problems

Both extend BaseAgentManager for consistent lifecycle.
Introduce TldrawAgentApp as the top-level coordinator class for the agent
system, replacing the old agentsAtom and useTldrawAgent patterns:

TldrawAgentApp:
  - Coordinator class for a given editor instance
  - Manages app-level concerns shared across agents
  - Creates and disposes of agents and persistence managers
  - Lifecycle tied to editor (dispose on crash/dispose)

AgentAppAgentsManager:
  - Multi-agent support with agent registry by id
  - Create, get, delete agent instances
  - Global agent lookup for cross-agent communication
  - Default agent creation with id="default"

AgentAppPersistenceManager:
  - State persistence using IndexedDB
  - Auto-save on changes with debouncing
  - Load/save agent state on mount
  - Persist: chat history, todos, context, model, debug flags

This architecture supports:
- Multiple agents per editor (multi-agent scenarios)
- Clean separation between app-level and agent-level concerns
- Automatic persistence without manual save calls
- Proper cleanup on editor disposal
Refactor TldrawAgent to use the new manager-based architecture:

TldrawAgent.ts:
  - Decomposed into focused managers for each concern
  - Managers: actions, chat, chatOrigin, context, debug, lints,
    mode, modelName, requests, todos, userAction
  - Prompt preparation using mode-based part filtering
  - Request handling with action streaming and masking
  - Schedule/interrupt pattern for chained requests
  - State serialization for persistence

TldrawAgentAppProvider.tsx:
  - React provider for TldrawAgentApp
  - useAgentApp() hook for app access
  - useAgent() hook for default agent access
  - Automatic cleanup on unmount

AgentHelpers.ts (moved from shared to client):
  - Helper utilities for action implementations
  - Shape lookup by id
  - Bounds calculation
  - Viewport management
  - Screenshot capture
  - Shape selection helpers

This completes the manager-based refactoring, with each manager
handling a specific concern and the agent coordinating between them.
Update all UI components to use the new TldrawAgentAppProvider and
manager-based architecture:

App.tsx:
  - Wrap with TldrawAgentAppProvider
  - Use useAgentApp() and useAgent() hooks

ChatPanel.tsx, ChatInput.tsx:
  - Use agent managers for state access
  - Update icon imports from shared/icons

ChatHistory components:
  - Update for new ChatHistoryItem types
  - Support continuation items
  - Update getActionInfo for new action types

Highlight components:
  - Use agent.context manager for items
  - Update icon imports

TargetAreaTool, TargetShapeTool:
  - Use agent.context.addItem() for context management
  - Update for new context item types

CustomHelperButtons, GoToAgentButton:
  - Use agent managers for state
  - Update icon imports

TodoList, PromptTag:
  - Use agent.todos manager
  - Update icon imports

index.css:
  - Add styles for new components
  - Update z-index for proper layering
Update the README to document the new manager-based architecture:

- Architecture overview with TldrawAgentApp and TldrawAgent
- Manager pattern explanation (actions, chat, context, etc.)
- Mode system for action masking
- Prompt part system (definitions + utils)
- Action schema system
- Multi-agent support
- Persistence system
- Updated API examples

Includes diagrams and code examples for extending the agent with
custom actions, modes, and prompt parts.
@max-drake max-drake force-pushed the max/agent-fairy-backport-clean branch from 14073e5 to d551211 Compare January 22, 2026 19:40
@max-drake max-drake changed the title WIP fairy agent backport checkpoint refactor(agent): restructure with manager-based architecture and mode system Jan 22, 2026
@max-drake max-drake changed the title refactor(agent): restructure with manager-based architecture and mode system Agent fairy backport Jan 22, 2026
@max-drake max-drake mentioned this pull request Jan 27, 2026
Copy link
Copy Markdown
Member

@mimecuvalo mimecuvalo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thx for backporting the fixes! got one little comment before landing, pre-approving and let's shipppp 🚢
let's be sure to add this to our QA sesh before the next SDK release 👍

@vercel vercel bot temporarily deployed to Preview – chat-template February 2, 2026 14:55 Inactive
@vercel vercel bot temporarily deployed to Preview – workflow-template February 2, 2026 14:55 Inactive
@vercel vercel bot temporarily deployed to Preview – tldraw-shader February 2, 2026 14:55 Inactive
@max-drake max-drake enabled auto-merge February 2, 2026 14:55
@max-drake max-drake added this pull request to the merge queue Feb 2, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

// always toast the error
this.agent.onError(error)
promise = null
throw error // you may not want to throw in productions
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate error toasts due to redundant error handling

Medium Severity

When an action throws an error, the error handler is invoked twice. In AgentActionManager.act(), the catch block calls this.agent.onError(error) and then re-throws the error. The outer catch in TldrawAgent.requestAgentActions() then catches the same error and calls this.onError(e) again. Since both reference the same error handler passed to the TldrawAgent constructor, users see duplicate error toasts for a single failure.

Additional Locations (1)

Fix in Cursor Fix in Web

* Manages debug functionality for an agent.
* Provides flags for controlling logging of system prompts, messages, and actions.
*/
export class AgentDebugManager {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AgentDebugManager inconsistent with manager base class pattern

Low Severity

AgentDebugManager is the only manager class that doesn't extend BaseAgentManager. All other managers (AgentActionManager, AgentChatManager, AgentChatOriginManager, AgentContextManager, AgentLintManager, AgentModeManager, AgentModelNameManager, AgentTodoManager, AgentUserActionTracker) extend the base class. This breaks the established inheritance pattern and means AgentDebugManager doesn't have access to the disposables set for cleanup tracking.

Fix in Cursor Fix in Web

!action.originY
) {
return
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resize and rotate skip actions with origin at zero

Low Severity

The falsy checks !action.originX and !action.originY incorrectly skip actions when the origin coordinates are legitimately 0. The coordinates represent positions relative to the chat origin, so originX: 0, originY: 0 is a valid point (the top-left of where the user started the chat). Using ! to check for undefined also catches the valid value 0, preventing resize/rotate operations centered at that position.

Additional Locations (1)

Fix in Cursor Fix in Web

Merged via the queue into main with commit 4dc80d2 Feb 2, 2026
19 of 20 checks passed
@max-drake max-drake deleted the max/agent-fairy-backport-clean branch February 2, 2026 15:10
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants