apps/docs/content/releases/next.mdx
This major release introduces a first-class theme system with display values, a new OverlayUtil system for canvas overlays, and extensibility APIs for custom record types, asset types, geo shapes, and frame-like shapes. Two new packages ship alongside it: @tldraw/driver for imperative editor control and @tldraw/mermaid for converting Mermaid diagrams into native shapes. It also adds a shape attribution system with the TLUserStore provider, clipboard and performance-measurement hooks, WebSocket hibernation in tlsync, RTL support, cross-window embedding, and various other improvements and bug fixes.
A new first-class theme system replaces the previous approach where colors were hardcoded and resolved inline. Themes are named, registered objects that shape utils consume via a structured display values pipeline.
Register custom themes via TLThemes module augmentation for type-safe IDs, add or remove palette colors via TLThemeDefaultColors and TLRemovedDefaultThemeColors, and pass themes to the editor via the themes and initialTheme props:
<Tldraw themes={{ corporate: myCorporateTheme }} initialTheme="corporate" />
Each default shape util defines getDefaultDisplayValues to resolve visual properties (colors, stroke widths, font sizes) from the current theme and color mode. Override display values for a default shape with getCustomDisplayValues:
const MyDrawShapeUtil = DrawShapeUtil.configure({
getCustomDisplayValues(_editor, _shape, _theme, _colorMode) {
return { strokeWidth: 10 }
},
})
The inferDarkMode prop has been renamed to colorScheme and changed from boolean to 'light' | 'dark' | 'system':
// Before
<Tldraw inferDarkMode />
// After
<Tldraw colorScheme="system" />
useIsDarkMode() has been renamed to useColorMode() and returns 'dark' | 'light' instead of boolean.
getDefaultColorTheme() and DefaultColorThemePalette have been removed. Use editor.getCurrentTheme().colors[colorMode] instead:
// Before
const theme = getDefaultColorTheme({ isDarkMode })
// After
const theme = editor.getCurrentTheme()
const colors = theme.colors[editor.getColorMode()]
useDefaultColorTheme() has been removed. Use editor.getCurrentTheme() and useColorMode() instead.
FONT_FAMILIES, FONT_SIZES, LABEL_FONT_SIZES, STROKE_SIZES, TEXT_PROPS, and ARROW_LABEL_FONT_SIZES have been removed — these are now resolved via display values.
SvgExportContext.themeId has been renamed to SvgExportContext.colorMode and changed from string to 'light' | 'dark'.
getColorValue() now takes TLThemeColors as its first argument instead of TLDefaultColorTheme.
A new OverlayUtil system unifies canvas overlays. Built-in brushes, handles, scribbles, snap indicators, selection foreground, collaborator cursors, and arrow hints are now implemented as overlay utils and can be customized or extended via the overlayUtils prop.
Overlays now render in front of indicators in a unified CanvasOverlays layer, and the legacy default component implementations and their CSS have been removed.
The legacy overlay components have been removed from TLEditorComponents. Canvas overlays are now rendered directly to a 2D canvas context by OverlayUtil classes, and customization happens by subclassing the default util rather than swapping a React component.
Customizing a built-in overlay: extend the default util and give it the same static type; it replaces the built-in when passed via overlayUtils:
// Before
const components: TLEditorComponents = {
Brush: MyBrushComponent,
}
<Tldraw components={components} />
// After
import { Tldraw, BrushOverlayUtil, type TLBrushOverlay } from 'tldraw'
class MyBrushOverlayUtil extends BrushOverlayUtil {
override render(ctx: CanvasRenderingContext2D, overlays: TLBrushOverlay[]) {
const overlay = overlays[0]
if (!overlay) return
const { x, y, w, h } = overlay.props
const zoom = this.editor.getEfficientZoomLevel()
ctx.fillStyle = 'rgba(0, 0, 255, 0.1)'
ctx.strokeStyle = 'blue'
ctx.lineWidth = 1 / zoom
ctx.beginPath()
ctx.rect(x, y, w, h)
ctx.fill()
ctx.stroke()
}
}
<Tldraw overlayUtils={[MyBrushOverlayUtil]} />
The following slots have been removed from TLEditorComponents. Subclass the matching default overlay util instead:
Brush, ZoomBrush → BrushOverlayUtil, ZoomBrushOverlayUtilScribble → ScribbleOverlayUtilSnapIndicator → SnapIndicatorOverlayUtilHandle, Handles → ShapeHandleOverlayUtilSelectionForeground, SelectionBackground → SelectionForegroundOverlayUtilCollaboratorHint → CollaboratorHintOverlayUtil (collaborator cursors/brushes/scribbles have their own utils too)The corresponding Default* exports and LiveCollaborators have also been removed.
Customizing shape indicators — ShapeIndicator, ShapeIndicators, and ShapeIndicatorErrorFallback are gone. Indicators now render through each shape util's getIndicatorPath(). Override it to change how a shape's indicator is drawn.
Overlays cannot render React; they draw into a 2D canvas context. For React-based overlays, render an HTML layer above the canvas via a regular TLEditorComponents slot like InFrontOfTheCanvas.
Overlay colors come from TLTheme, not CSS variables. Read them inside render() via this.editor.getCurrentTheme().colors[this.editor.getColorMode()] so overlays follow light/dark mode automatically.
These CSS variables have been removed: --tl-color-snap, --tl-color-brush-fill, --tl-color-brush-stroke, --tl-color-laser, --tl-layer-overlays-custom.
Overriding them (or the removed selectors .tl-brush, .tl-scribble, .tl-snap-indicator, .tl-handle*, .tl-selection__fg__outline, .tl-corner-handle, .tl-text-handle, .tl-corner-crop-handle, .tl-mobile-rotate__*) no longer has any effect. Port these to TLTheme entries or to the overlay util's render().
You can now register custom record types in the tldraw store for persisting and synchronizing domain-specific data that doesn't fit into shapes, bindings, or assets. Custom records support scoping (document/session/presence), validation, migrations, and default properties.
import { createTLSchema, createCustomRecordId } from 'tldraw'
const schema = createTLSchema({
records: [
{
typeName: 'marker',
scope: 'document',
validator: markerValidator,
},
],
})
TypeScript module augmentation via TLGlobalRecordPropsMap lets custom record types participate in the TLRecord union.
GeoShapeUtil now supports custom geo types via a customGeoTypes option on configure(). Previously, adding a new geo type required forking or monkey-patching GeoShapeUtil; now custom types plug into the existing system and inherit labels, resizing, fill/dash/color styling, SVG export, and hyperlink support — while supplying their own path geometry, snap behavior, creation size, and style panel icon.
import { GeoShapeUtil, PathBuilder } from 'tldraw'
const MyGeoShapeUtil = GeoShapeUtil.configure({
customGeoTypes: {
'rounded-rect': {
getPath: (w, h, shape) => {
const r = Math.min(w, h) * 0.2
return new PathBuilder()
.moveTo(r, 0, { geometry: { isFilled: shape.props.fill !== 'none' } })
.lineTo(w - r, 0)
.circularArcTo(r, false, true, w, r)
.lineTo(w, h - r)
.circularArcTo(r, false, true, w - r, h)
.lineTo(r, h)
.circularArcTo(r, false, true, 0, h - r)
.lineTo(0, r)
.circularArcTo(r, false, true, r, 0)
.close()
},
snapType: 'polygon',
icon: 'geo-rectangle',
defaultSize: { w: 200, h: 150 },
},
},
})
<Tldraw shapeUtils={[MyGeoShapeUtil]} />
Each definition supplies getPath, snapType ('polygon' for vertex + center snaps, 'blobby' for center-only), and an icon for the style panel, plus optional defaultSize and onDoubleClick handler. Custom types appear alongside the built-ins in the geo picker.
A new AssetUtil base class follows the ShapeUtil / BindingUtil pattern, making the asset system extensible. Previously, assets were a hardcoded union of image, video, and bookmark types. Now you can register custom asset types with their own MIME type handling, file-to-asset conversion, and shape creation logic.
import { AssetUtil } from '@tldraw/editor'
class AudioAssetUtil extends AssetUtil<TLAudioAsset> {
static override type = 'audio' as const
override getSupportedMimeTypes() {
return ['audio/mpeg', 'audio/wav']
}
override async getAssetFromFile(editor, file) {
/* ... */
}
}
;<Tldraw assetUtils={[AudioAssetUtil]} />
TLAssetStore keeps upload/resolve/remove as cross-cutting concerns, while AssetUtil handles type-specific behavior: MIME types, file-to-asset metadata, and asset-to-shape creation.
assetValidator has been removed. Use imageAssetValidator, videoAssetValidator, or bookmarkAssetValidator instead.
getMediaAssetInfoPartial has been removed. Use AssetUtil.getAssetFromFile instead.
notifyIfFileNotAllowed signature changed from (file, options) to (editor, file, options).
getAssetInfo signature changed from (file, options, assetId?) to (editor, file, assetId?) and now returns TLAsset | null instead of throwing.
Custom shapes can now opt into frame-like behavior: clipping children, acting as a parent on paste and drag-in, blocking erasure from inside, and supporting full-brush selection. Previously, frame behavior was hardcoded to the built-in frame type; the editor and tools now route frame checks through editor.getShapeUtil(shape).isFrameLike(shape).
The easiest way to build one is to extend the new BaseFrameLikeShapeUtil abstract class, which provides sensible defaults for isFrameLike, providesBackgroundForChildren, canReceiveNewChildrenOfType, getClipPath, onDragShapesIn, and onDragShapesOut:
import { BaseFrameLikeShapeUtil, SVGContainer } from '@tldraw/editor'
class MyContainerUtil extends BaseFrameLikeShapeUtil<MyContainerShape> {
static override type = 'my-container' as const
static override props = myContainerShapeProps
override getDefaultProps() {
return { w: 300, h: 200 }
}
override component(shape: MyContainerShape) {
return <SVGContainer>...</SVGContainer>
}
override indicator(shape: MyContainerShape) {
return <rect width={shape.props.w} height={shape.props.h} />
}
}
Shapes that need frame behavior without inheriting from BaseFrameLikeShapeUtil can override isFrameLike() directly on any ShapeUtil. FrameShapeUtil now extends BaseFrameLikeShapeUtil, so existing frames are unaffected.
A new shape attribution system tracks who created and last edited shapes. The TLUserStore provider interface connects tldraw to your auth system with reactive Signal-based methods: getCurrentUser() returns the active user for presence and attribution, while resolve(userId) resolves any user ID to display info.
<Tldraw
users={{
getCurrentUser: () => currentUserSignal,
resolve: (userId) => resolvedUserSignal(userId),
}}
/>
User records are now document-scoped via the unified TLUser record type. SDK users can extend user records with custom validated metadata through createTLSchema:
const schema = createTLSchema({
user: {
meta: {
isAdmin: T.boolean,
department: T.string,
},
},
})
Note shapes now track and display a "first edited by" attribution label in the bottom-right corner, showing who first added text to the note.
A new @tldraw/driver package provides an imperative API for driving the tldraw editor programmatically. Driver wraps an Editor instance and exposes event dispatch, selection transforms, clipboard operations, and shape queries with fluent chaining.
import { Driver } from '@tldraw/driver'
const driver = new Driver(editor)
driver.pointerMove(100, 100).pointerDown().pointerMove(200, 200).pointerUp()
This is useful for scripting, automation, agent workflows, and REPL-style interaction. The release includes a Scripter example that demonstrates the package.
A new @tldraw/mermaid package converts Mermaid diagram syntax into native tldraw shapes. Paste Mermaid text to create flowcharts, sequence diagrams, state diagrams, and mind maps as editable shapes on the canvas.
import { createMermaidDiagram } from '@tldraw/mermaid'
await createMermaidDiagram(
editor,
`
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[End]
`
)
The package parses Mermaid syntax, extracts layout from the rendered SVG, and produces a diagram-agnostic blueprint that gets rendered into geo shapes, arrows, and groups.
Node creation is extensible: pass mapNodeToRenderSpec per diagram type to map diagram nodes to different shapes, or use createShape in BlueprintRenderingOptions to take full control of how nodes are created on the canvas.
Paste any <iframe> embed code onto the canvas to create an embed shape. Previously only URLs matching known providers (YouTube, Google Maps, etc.) worked. Now any valid iframe with an HTTP(S) source creates an embed directly. It works with services like OpenStreetMap, SoundCloud, Loom, and more.
New TldrawOptions hooks let you intercept and customize clipboard copy, cut, and paste. onBeforeCopyToClipboard filters or transforms serialized content before it hits the clipboard, onBeforePasteFromClipboard filters parsed paste payloads before shapes are created, and onClipboardPasteRaw handles raw clipboard data before tldraw's default paste pipeline.
const options: Partial<TldrawOptions> = {
onBeforeCopyToClipboard(info, content) {
// filter shapes or transform content before copy/cut
return content
},
onBeforePasteFromClipboard(info, content) {
// filter or transform parsed paste content
return content
},
onClipboardPasteRaw(info) {
// handle raw clipboard data yourself; return false to cancel tldraw handling
return false
},
}
Tldraw now works correctly when embedded in iframes, Electron pop-out windows, and Obsidian plugins where the global document and window differ from the ones tldraw is mounted in. All bare document and window references have been replaced with container-aware alternatives.
New helpers getOwnerDocument() and getOwnerWindow() are exported from @tldraw/editor, along with Editor.getContainerDocument() and Editor.getContainerWindow() convenience methods.
A new editor.performance API lets SDK users subscribe to editor performance events: interaction frame times (translating, resizing, drawing), camera pan/zoom sessions, shape create/update/delete operations, and undo/redo timings. Events aggregate frame-time percentiles (avg / median / p95 / p99) and, on Chromium, attach Long Animation Frame data with per-script attribution for diagnosing jank.
const unsubscribe = editor.performance.on('interaction-end', (event) => {
// forward to analytics
const { frameTimes, longAnimationFrames, ...metrics } = event
track('editor.interaction', metrics)
})
The manager is zero-overhead when no listeners are attached. Custom tools opt in to interaction tracking via static trackPerformance = true on the StateNode. A PerformanceApiAdapter is also available for piping events into the browser Performance API for DevTools timelines.
The tldraw UI now supports right-to-left languages like Arabic. A new useDirection() hook returns 'ltr' or 'rtl' from the current translation context, and all Radix UI components and CSS have been updated to respect text direction. The dir attribute is set on .tl-container, and CSS uses logical properties (margin-inline-start, inset-inline-end, etc.) instead of physical ones.
TLSocketRoom now supports session resume and snapshot APIs for WebSocket hibernation environments like Cloudflare Durable Objects. Sessions can be suspended and restored without losing state, and the sync-cloudflare template has been updated to use the WebSocket Hibernation API.
New APIs include handleSocketResume() for restoring sessions from snapshots, getSessionSnapshot() for capturing session state, and an onSessionSnapshot callback for persisting snapshots to WebSocket attachments.
defaultColorNames, DefaultColorThemePalette, DefaultLabelColorStyle, TLDefaultColorTheme type, and getDefaultColorTheme() from @tldraw/tlschema. Use editor.getCurrentTheme().colors instead. (#8410)ARROW_LABEL_FONT_SIZES, FONT_FAMILIES, FONT_SIZES, LABEL_FONT_SIZES, STROKE_SIZES, TEXT_PROPS, and useDefaultColorTheme() from @tldraw/tldraw. These are now resolved via display values. (#8410)inferDarkMode prop to colorScheme (boolean → 'light' | 'dark' | 'system'). Rename useIsDarkMode() to useColorMode() (returns 'dark' | 'light' instead of boolean). (#8410)SvgExportContext.themeId to SvgExportContext.colorMode (string → 'light' | 'dark'). (#8410)getColorValue() first argument from TLDefaultColorTheme to TLThemeColors. (#8410)PlainTextLabelProps and RichTextLabelProps: font → fontFamily, align → textAlign, fill removed. (#8410)Brush, ZoomBrush, Scribble, SnapIndicator, Handle, Handles, SelectionForeground, SelectionBackground, CollaboratorHint, ShapeIndicator, ShapeIndicators, and ShapeIndicatorErrorFallback from TLEditorComponents, along with the corresponding Default* component exports and LiveCollaborators. Migrate custom overlay components to OverlayUtil; indicator customization moves to ShapeUtil.getIndicatorPath(). (#8469)--tl-color-snap, --tl-color-brush-fill, --tl-color-brush-stroke, --tl-color-laser, and --tl-layer-overlays-custom, along with the overlay class selectors (.tl-brush, .tl-scribble, .tl-snap-indicator, .tl-handle*, .tl-selection__fg__outline, .tl-corner-handle, .tl-text-handle, .tl-corner-crop-handle, .tl-mobile-rotate__*). Overlay colors now come from TLTheme. (#8469)assetValidator. Use imageAssetValidator, videoAssetValidator, or bookmarkAssetValidator instead. (#8031)getMediaAssetInfoPartial. Use AssetUtil.getAssetFromFile instead. (#8031)notifyIfFileNotAllowed signature from (file, options) to (editor, file, options). (#8031)getAssetInfo signature from (file, options, assetId?) to (editor, file, assetId?) and return TLAsset | null instead of throwing. (#8031)Cmd+Shift+C / Ctrl+Shift+C shortcut from "Copy as SVG" to "Copy as PNG". (#8532)TLTheme, TLThemeId, TLThemes, TLThemeDefaultColors, TLThemeColors, TLRemovedDefaultThemeColors, ThemeManager, getDisplayValues(), getColorValue(), and DEFAULT_THEME for the new theme system. (#8410)themes and initialTheme props to <Tldraw> and <TldrawEditor>. (#8410)getCurrentTheme(), setCurrentTheme(), getThemes(), getTheme(), updateTheme(), updateThemes(), and getColorMode() to the editor. (#8410)getDefaultDisplayValues and getCustomDisplayValues to shape util options for theme-aware visual properties. (#8410)OverlayUtil, TLOverlay, OverlayManager, Editor.overlays, defaultOverlayUtils, and the overlayUtils prop on <Tldraw> / <TldrawEditor> for registering and managing canvas overlays. (#8469)BrushOverlayUtil, ZoomBrushOverlayUtil, ScribbleOverlayUtil, SnapIndicatorOverlayUtil, ShapeHandleOverlayUtil, SelectionForegroundOverlayUtil, ArrowHintOverlayUtil, CollaboratorBrushOverlayUtil, CollaboratorScribbleOverlayUtil, CollaboratorHintOverlayUtil, and CollaboratorCursorOverlayUtil. (#8469)TLTheme. (#8469)BaseFrameLikeShapeUtil abstract class and ShapeUtil.isFrameLike() so custom shapes can opt into frame-like behaviors (paste parenting, full-brush selection, blocking erasure from inside, clipping children). FrameShapeUtil now extends BaseFrameLikeShapeUtil. (#8331)GeoShapeUtil.configure({ customGeoTypes }). Add GeoTypeDefinition interface for defining path geometry, snap behavior, creation size, style panel icon, and double-click handler while inheriting standard geo shape behavior. (#8543)AssetUtil base class with configure(), getDefaultProps(), getSupportedMimeTypes(), getAssetFromFile(), and createShape(). Add assetUtils prop to <Tldraw>. Add Editor.getAssetUtil(), Editor.hasAssetUtil(), and Editor.getAssetUtilForMimeType(). Add TLGlobalAssetPropsMap for type-safe custom asset registration. Add createAssetRecordType(), defaultAssetSchemas, and assets parameter to createTLSchema(). (#8031)CustomRecordInfo interface, createCustomRecordId(), createCustomRecordMigrationIds(), createCustomRecordMigrationSequence(), isCustomRecord(), isCustomRecordId() for custom record types. createTLSchema() and createTLStore() now accept a records option. (#8213)TLUserStore interface with getCurrentUser() and resolve() for connecting tldraw to auth systems. Add unified TLUser record type, UserRecordType, createUserId, isUserId, userIdValidator, and createUserRecordType() for extensible user schemas. Add user parameter to createTLSchema(). Add Editor.getAttributionUser(), Editor.getAttributionUserId(), and Editor.getAttributionDisplayName(). Add textFirstEditedBy prop to TLNoteShapeProps. (#8147)@tldraw/driver package with Driver class for imperative editor control. (#7952)@tldraw/mermaid package with createMermaidDiagram(), renderBlueprint(), and MermaidDiagramError for converting Mermaid syntax to tldraw shapes. (#8194)mapNodeToRenderSpec per-diagram-type option and createShape override to @tldraw/mermaid for customizing how diagram nodes are rendered as shapes. (#8322)onBeforeCopyToClipboard, onBeforePasteFromClipboard, and onClipboardPasteRaw hooks to TldrawOptions for intercepting clipboard operations. Add TLClipboardWriteInfo and TLClipboardPasteRawInfo types. Export handleNativeOrMenuCopy from @tldraw/tldraw. (#8290)Cmd+Shift+V / Ctrl+Shift+V shortcut to paste clipboard content as plain text. Cmd+Shift+V no longer toggles paste-at-cursor positioning. (#8347)<iframe> embed codes to create embed shapes from any service. (#8306)DEFAULT_EMBED_DEFINITIONS so Canva design URLs paste as iframe embeds. (#8459)TldrawOptions.rightClickPanning (default true) to gate the new right-click drag-to-pan behavior. Add InputsManager.getIsRightPointing() / setIsRightPointing(). (#8501)Editor.performance (PerformanceManager) with on(), once(), and dispose() for subscribing to interaction, camera, shape-operation, frame, and undo/redo performance events. Add PerformanceApiAdapter for piping events into the browser Performance API. Add StateNode.trackPerformance static field so custom tools can opt into interaction tracking. Add TLPerfFrameTimeStats, TLPerfEventMap, TLInteractionStartPerfEvent, TLInteractionEndPerfEvent, TLCameraStartPerfEvent, TLCameraEndPerfEvent, TLShapeOperationPerfEvent, TLFramePerfEvent, TLUndoRedoPerfEvent, TLPerfLongAnimationFrame, and TLPerfLongAnimationFrameScript. (#8421)Editor.getContainerDocument() and Editor.getContainerWindow() methods, and getOwnerDocument() / getOwnerWindow() helpers for cross-window embedding. (#8196)handleSocketResume(), getSessionSnapshot(), and onSessionSnapshot to TLSocketRoom for WebSocket hibernation support. Add clientTimeout option to TLSyncRoom. (#8070)useDirection() hook for RTL support. (#8033)PeopleMenu, PeopleMenuItem, PeopleMenuFacePile, and UserPresenceEditor through TldrawUiComponents so the share panel sub-components can be overridden via the <Tldraw /> components prop. (#8346)'none' to TLDefaultDashStyle for hiding shape borders while preserving fills. The option is not exposed in the style panel — it can only be set programmatically. Add NonePathBuilderOpts; PathBuilder.toSvg() now returns null for the 'none' style. (#8453)defaultHandleExternalFileReplaceContent, which was previously referenced in docs but missing from the package's public exports. (#8579)TLSvgExportOptions.padding to accept number | 'auto'. The 'auto' mode (now default) renders with padding then trims to visual content bounds. (#8202)TextManager.measureHtmlBatch() for batched DOM text measurement. (#7949)'json' to TLCopyType for copying shapes as JSON in debug mode. (#8206)Vec.IsFinite() for checking whether a vector has finite coordinates. (#8176)Vec.PointsBetween() to accept an optional ease parameter. (#7977)Vec, Edge2d, Circle2d, Arc2d, Polyline2d, and intersection routines. Circle hit testing is up to 19x faster, polyline nearest-point is 6.8x faster. (#8210)Cmd+Shift+V / Ctrl+Shift+V shortcut to paste clipboard content as plain text, stripping HTML and rich formatting. Cmd+Shift+V no longer toggles paste-at-cursor positioning — the "Paste at cursor" preference covers that use case. (#8347)Cmd+Shift+C / Ctrl+Shift+C to copy selected shapes as PNG instead of SVG, matching the default in most apps. The SVG copy path is still available from the Copy-as menu. (#8532)TldrawOptions.rightClickPanning (defaults to true). (#8501)@use-gesture/react dependency with custom gesture handling. Bundle size is smaller and a stale dependency is gone. (#8392)bailToMark silently discarding pending history changes when the target mark doesn't exist. (#8260)FocusManager.dispose() not actually removing document event listeners due to .bind() creating new function references. (#8232)'moving' when the editor is disposed mid-transition (e.g. deep links under React strict mode), which blocked all pointer interactions on the canvas. (#8396)Editor instances were retained through a shared throttled updateHoveredShapeId closure. (#8439)Editor instances were retained through the window.__tldraw__hardReset global. (#8476)kickoutOccludedShapes during multiplayer sync. (#8448)OffscreenCanvas support (e.g. older Safari) when preloading image alpha data. (#8582)transform: scale() was applied to shape labels even when dynamic size was inactive, forcing an unnecessary compositing layer on every shape. (#8570)stopPropagation blocking pointer events. (#8519)Cmd+Shift+V doing nothing when the clipboard contains an image; the shortcut now falls back to the regular paste flow when there is no plain-text payload. (#8490)ResizeObserver loop browser warnings when hovering over toolbar buttons. (#8574)Cmd+V paste of tldraw-copied PNGs on Chrome 147 stable, where the custom-format clipboard entry returned a 0-byte blob. (#8615)hotkeys-js library with a small layout-aware native event matcher. (#8669)ShapeUtil base-class methods so overrides that need a shape parameter can declare it without errors. (#8521)