refactor(shapes): add display values system for shape visual properties#8121
refactor(shapes): add display values system for shape visual properties#8121steveruizok merged 12 commits intodefault-shape-customizationfrom
Conversation
… 134 broken links (#8103) In order to catch broken internal links before they ship (like the ones fixed in #8102), this PR replaces the `linkinator`-based HTTP crawler with a static checker that validates links against the content database. The old checker required a running server, couldn't fail the build, and missed most broken links. The new checker runs after `refresh-everything` and before `next build`, failing early if any broken links are found. Along the way, this fixes all 134 broken internal links found across the docs: - 25 content files with stale `/docs/*` paths (should be `/sdk-features/*`), wrong heading anchors, double-prefixed paths, and renamed example links - API doc generator skipping `TypeAlias`/`Interface` namespace members (the `// TODO: document these` that caused `T.Validatable` etc. to generate broken links) - Namespace member path generation producing standalone paths like `T-Validatable` instead of anchor links like `T#Validatable` The checker uses a `--fail` flag to control behavior: `yarn check-links --fail` (used in `build`) exits non-zero on broken links, while `yarn check-links` (used in dev) only warns. This ensures broken links never block the dev server from starting. Additionally, this adds a standalone `yarn check-external-links` script for manually checking external (HTTP/HTTPS) links across all docs content. It deduplicates URLs, uses HEAD with GET fallback (30s timeout, 10 concurrent requests), and retries transient failures with backoff. A skip list filters out domains that block automated requests (npmjs.com, shadertoy.com). This replaces the external link checking that was previously handled by linkinator but is too slow/flaky for the build pipeline. Also refactors the hardcoded Framer path prefixes into a shared `fetchFramerPaths()` utility that fetches the live Framer sitemap. Both the link checker and `app/sitemap.ts` now use this, so the list stays up to date automatically. Fixes a broken external link to the removed `custom-toolbar` example in the v2.1.0 release notes. ### Change type - [x] `improvement` ### Test plan 1. `cd apps/docs && yarn refresh-everything && yarn check-links` — should pass with 0 broken links (warnings only) 2. `yarn check-links --fail` — same result but would exit 1 if any broken links existed 3. Manually add a broken link to a content file (e.g. `[test](/nonexistent)`) and run `yarn check-links --fail` — should exit 1 with the broken link reported 4. Run `yarn build` from `apps/docs` — check-links runs with `--fail` before `next build` 5. `yarn check-external-links` — checks all ~630 unique external URLs, reports broken ones - [ ] Unit tests - [ ] End to end tests ### Release notes - Fix 134 broken internal links across SDK docs, starter kits, and API reference - Add static broken link checker to docs build pipeline (replaces linkinator) - Add standalone external link checker script (`yarn check-external-links`) - Document `TypeAlias` and `Interface` members within API reference namespaces
Closes #7472 Adds an example demonstrating how to create and customize text labels on arrows. The example covers: - Creating arrows with labels using `toRichText()` and the `richText` prop - Positioning labels along the arrow path with `labelPosition` (0 = start, 0.5 = middle, 1 = end) - Setting independent `labelColor` distinct from the arrow's `color` - All four font styles: `draw`, `sans`, `serif`, `mono` - Curved arrows with `bend` ### Change type - [x] `feature` ### Test plan 1. Run `yarn dev` and navigate to the "Arrow labels" example under Editor API 2. Verify arrows render with correct labels, positions, colors, and fonts 3. Verify the curved arrow displays with a bend and centered label ### Release notes - Add arrow labels example showing how to create and customize text labels on arrows. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Adds new example/demo code and documentation only, with no changes to core editor behavior. > > **Overview** > Adds a new Editor API example, **Arrow labels**, that seeds the canvas with several arrow shapes showcasing labeled arrows via `richText`/`toRichText()`. > > Demonstrates label customization including `labelPosition`, independent `labelColor`, multiple `font` styles, and a curved (`bend`) arrow, plus a matching `README.md` to surface the example in the examples catalog. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit fea149b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Closes #8100 The `ShapesWithSVGs` component (used when Debug SVG is enabled) renders `<Shape>` without a `<ShapeCullingProvider>` ancestor. Since the `Shape` component calls `useShapeCulling()`, this throws: "useShapeCulling must be used within ShapeCullingProvider". The `ShapesToDisplay` component already had this provider but `ShapesWithSVGs` was missing it. This wraps `ShapesWithSVGs` content with `<ShapeCullingProvider>`, `<CullingController>`, and the Safari reflow workaround — matching what `ShapesToDisplay` already does. ### Change type - [x] `bugfix` ### Test plan 1. Open tldraw and create some shapes on the canvas 2. Open the debug menu and enable **Debug mode** 3. Enable **Debug SVG** 4. Verify the app no longer crashes and SVG debug copies appear below each shape ### Release notes - Fix crash when enabling Debug SVG with shapes on the canvas. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: localized change to canvas shape rendering that mainly affects debug-only `Debug SVG` mode and reuses existing culling logic. > > **Overview** > Fixes a crash when enabling **Debug SVG** by ensuring shapes rendered in that mode are wrapped in `ShapeCullingProvider` (so `useShapeCulling()` always has a provider). > > Simplifies the canvas rendering path by replacing the separate `ShapesWithSVGs`/`ShapesToDisplay` branches with a single `ShapesLayer` that toggles debug SVG copies internally while still running `CullingController` and the Safari reflow workaround. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit aba4a4e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Introduce the foundational system for shape display values — a pattern that lets each ShapeUtil declare how its visual properties (colors, fonts, stroke widths, etc.) are derived from shape props and theme. - Add ShapeOptionsWithDisplayValues interface and getDisplayValues() helper that merges base values with user-provided overrides - Add DEFAULT_FILL_COLOR_NAMES mapping for fill style → theme color - Add PatternFill component to replace the pattern case from ShapeFill - Move P3 color space detection from a React hook (useColorSpace) into a reactive atom on tlenvReactive, so it can be read outside React - Update RichTextLabel and PlainTextLabel to accept resolved display values (fontFamily, textAlign, verticalAlign) instead of style tokens - Remove ShapeFill (superseded by per-shape fill rendering) and useColorSpace (superseded by tlenvReactive.supportsP3ColorSpace)
Refactor GeoShapeUtil to derive all visual properties through display values. This is the most complex shape — it has stroke, fill, and a rich text label with alignment, font, and sizing configuration. - Define GeoShapeUtilDisplayValues and GeoShapeUtilOptions interfaces - Move label sizing, growY calculation, and shape expansion logic from module-level functions into private methods on the util class so they can access display values through this.options - Replace direct theme/constant lookups with display value properties - Move GeoShapeBody from components/ subdirectory to flat file, accepting resolved colors as props instead of reading theme internally
Refactor NoteShapeUtil to derive visual properties through display values. The note shape is unique because its dimensions (noteWidth, noteHeight) are themselves configurable, and several external callers (noteHelpers, Pointing tool state) need access to those dimensions. - Define NoteShapeUtilDisplayValues with note dimensions, background color, border, and label properties - Move label sizing and font adjustment logic into private methods - Refactor noteHelpers to accept note dimensions via options objects instead of importing NOTE_SIZE/NOTE_CENTER_OFFSET constants - Update Pointing tool state to resolve note dimensions from the util
Refactor ArrowShapeUtil to derive visual properties through display values. Arrows have stroke, fill (for arrowheads), and a label with font, padding, and border radius configuration. - Define ArrowShapeUtilDisplayValues interface in arrow-types.ts and extend ArrowShapeOptions with ShapeOptionsWithDisplayValues - Pass resolved colors and dimensions to the ArrowSvg component as props instead of reading theme internally - Update arrowLabel.ts to resolve font and padding from display values instead of importing constants directly - Remove getArrowLabelFontSize (superseded by display values)
Refactor DrawShapeUtil and HighlightShapeUtil to derive visual properties through display values. - DrawShapeUtil: define display values for stroke color/width, fill color, and pattern fallback; pass resolved values to DrawShapeSvg - HighlightShapeUtil: define display values for stroke color/width and opacity; move P3 color selection into getDisplayValues using the new tlenvReactive.supportsP3ColorSpace; move underlayOpacity and overlayOpacity from static options to display values
…peUtil Refactor the remaining shape utils to derive visual properties through display values. - TextShapeUtil: define display values for color, font family, size, line height, and font style; pass resolved values to text measurement - LineShapeUtil: define display values for stroke color and width - FrameShapeUtil: define display values for fill, stroke, and heading colors in both default and showColors modes
Add empty display value implementations to shapes that don't yet have visual properties to expose (bookmark, embed, image, video), ensuring all built-in shapes conform to the ShapeOptionsWithDisplayValues interface. Also wire up the EmbedShapeUtil's showShadow as a display value. Update SelectTool child states (PointingHandle, Translating) to resolve note dimensions from the NoteShapeUtil's display values instead of importing the removed NOTE_SIZE and NOTE_CENTER_OFFSET constants.
- Export all new display value types and interfaces from the tldraw package entry point - Update both editor and tldraw API reports to reflect the new public API surface - Add a display-options example demonstrating how to use getDisplayValueOverrides to customize shape rendering - Update configure-shape-util example README to reference the renamed NoteShapeUtilOptions - Update grid-align-on-create test to use inline note size constant instead of the removed NOTE_SIZE export
|
The latest updates on your projects. Learn more about Vercel for GitHub.
5 Skipped Deployments
|
Update the API report to include a new `labelExtraPadding` property on `GeoShapeUtilDisplayValues`. This reflects an added display option for extra padding around geo-shape labels and keeps the generated API docs in sync with the code.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| // We cast here because editor.getShapeUtil('arrow') returns ShapeUtil<TLArrowShape> | ||
| // with generic options, but the actual instance has ArrowShapeOptions with display values. | ||
| return getDisplayValues(editor.getShapeUtil('arrow') as any, shape, false) | ||
| } |
There was a problem hiding this comment.
Arrow label sizing always uses light mode display values
Medium Severity
getArrowDisplayValues hardcodes isDarkMode to false and is used in labelSizeCache (a createComputedCache). The cache is keyed only by shape record, not by dark mode state. If a consumer provides getDisplayValueOverrides that changes size-related properties (like labelFontSize, labelPadding, or labelFontFamily) based on isDarkMode, the cached label size will always reflect light-mode values. This causes incorrect label sizing, geometry, and clip paths in dark mode for consumers using the new override system.
Additional Locations (1)
| ? 'center' | ||
| : verticalAlign === 'end' | ||
| ? 'flex-end' | ||
| : 'flex-start', |
There was a problem hiding this comment.
Legacy alignment values handled inconsistently between label components
Low Severity
RichTextLabel now receives pre-mapped textAlign values where legacy alignments like 'start-legacy' are mapped to 'start', resulting in justifyContent: 'flex-start'. Previously, ALL legacy alignment values produced justifyContent: 'center' due to the legacyAlign catch-all. Meanwhile, PlainTextLabel preserves the old behavior where legacy values still map to 'center'. Shapes with 'start-legacy' or 'end-legacy' alignment will now render with different text justification in RichTextLabel compared to before, and differently from PlainTextLabel.
Additional Locations (1)
Update `next.mdx` release notes to cover all SDK-relevant PRs merged to main since v4.4.0. Highlights: - Display values system (#8121) with breaking changes and migration guide - Click-through on transparent image pixels (#7942) - `Editor.resizeToBounds()` (#8120) - SVG sanitization (#7896) - TypeScript enum-to-const refactoring (#8084) - 14 bug fixes and 4 improvements ### Change type - [x] `other` ### Test plan 1. Verify the release notes render correctly on the docs site ### Release notes - Update next release notes with changes since v4.4.0. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: documentation-only changes updating release notes content and date, with no runtime/code behavior impact. > > **Overview** > Updates `apps/docs/content/releases/next.mdx` for the upcoming release by refreshing the date and replacing the brief blurb with expanded release notes. > > Documents new SDK surface area (`Geometry2d.ignoreHit`, `Editor.resizeToBounds`, `sanitizeSvg`), highlights click-through on transparent image pixels, and adds a list of recent improvements and bug fixes (paste parenting, link/alt-text persistence, SVG sanitization behavior, circular-dependency cleanup, and several crash/export/sync fixes). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5f7dc0a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->


In order to make shape visual properties (colors, fonts, stroke widths, etc.) configurable and overridable by SDK consumers, this PR introduces a display values system. Each ShapeUtil now declares a
getDisplayValues()function that derives all visual properties from shape props and theme, and agetDisplayValueOverrides()function that consumers can use viaShapeUtil.configure()to customize any visual property.This replaces the previous pattern where shape utils directly read theme colors and style constants internally, making those decisions opaque to consumers. Now every visual property flows through a single, configurable pipeline.
Original branch:
styles-2-cleanCode changes
getDisplayValues()helper andShapeOptionsWithDisplayValuesinterface. NewPatternFillcomponent anddefaultFillsmapping. P3 color space detection moved from React hook totlenvReactive.RichTextLabel/PlainTextLabelaccept resolved values instead of style tokens. RemovedShapeFillanduseColorSpace.display-optionsexample demonstratinggetDisplayValueOverrides. Updatedconfigure-shape-utilREADME.grid-align-on-create.test.tsto use inline note size instead of removedNOTE_SIZEexport.index.tsexports, both API reports, eslint-disable comments for empty interfaces.Change type
apiTest plan
Release notes
ShapeUtil.configure()getDisplayValues()andShapeOptionsWithDisplayValuesto public APIgetDisplayValueOverridesoption for overriding colors, fonts, sizing, and other visual properties per shapetlenvReactive.supportsP3ColorSpaceRichTextLabelandRichTextSVGprops changed from style tokens to resolved valuesNoteShapeOptionsrenamed toNoteShapeUtilOptionsHighlightShapeOptions.underlayOpacityandoverlayOpacitymoved to display valuesShapeFillcomponent,useColorSpacehook,NOTE_SIZEandNOTE_CENTER_OFFSETconstants,getArrowLabelFontSizefunctionAPI changes
ShapeOptionsWithDisplayValues<Shape, DisplayValues>interfacegetDisplayValues()helper functionGeoShapeUtilDisplayValues,NoteShapeUtilDisplayValues,ArrowShapeUtilDisplayValues,DrawShapeUtilDisplayValues,HighlightShapeUtilDisplayValues,TextShapeUtilDisplayValues,LineShapeUtilDisplayValues,FrameShapeUtilDisplayValues,EmbedShapeUtilDisplayValues,BookmarkShapeUtilDisplayValues,ImageShapeUtilDisplayValues,VideoShapeUtilDisplayValuesGeoShapeUtilOptions,NoteShapeUtilOptions,LineShapeUtilOptions,EmbedShapeUtilOptions,BookmarkShapeUtilOptions,ImageShapeUtilOptionstlenvReactive.supportsP3ColorSpaceRichTextLabelPropsto usefontFamily,textAlign,verticalAlign(resolved strings) instead offont,align,verticalAlign(style tokens)RichTextSVGPropssimilarly, addinglineHeightNoteShapeOptions→NoteShapeUtilOptionsHighlightShapeOptions.underlayOpacity/overlayOpacitytoHighlightShapeUtilDisplayValuesShapeFill,PatternFill(from ShapeFill.tsx),useColorSpace,NOTE_SIZE,NOTE_CENTER_OFFSET,getArrowLabelFontSizeNote
High Risk
Large refactor across most built-in shape utils plus multiple public API and prop-type breaking changes (e.g. label components and shape option types), which can affect rendering/export behavior and downstream integrators.
Overview
Introduces a "display values" system so shape visuals (colors, fonts, stroke widths, fills, alignment, etc.) are computed via per-
ShapeUtilgetDisplayValues()and consumer-overridablegetDisplayValueOverrides(), with a sharedgetDisplayValues()helper exported in the public API.Refactors built-in shape utils (e.g.
Geo,Arrow,Note,Draw,Highlight,Text,Line,Frame) and related helpers to render from these resolved values (including newPatternFill/fill mapping and P3 color-gamut detection viatlenvReactive.supportsP3ColorSpace), and updates exports/docs plus a new examples entry demonstrating display overrides.Written by Cursor Bugbot for commit 5c5aba4. This will update automatically on new commits. Configure here.