Skip to content

[pull] main from tldraw:main#608

Merged
pull[bot] merged 5 commits into
code:mainfrom
tldraw:main
Jun 20, 2026
Merged

[pull] main from tldraw:main#608
pull[bot] merged 5 commits into
code:mainfrom
tldraw:main

Conversation

@pull

@pull pull Bot commented Jun 20, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

steveruizok and others added 5 commits June 19, 2026 22:04
…ers) (#9347)

In order to make tldraw.com files open without any animated loading
state, this PR removes the editor load animations from the dotcom tla
app: the fade-in shroud and the loading screens that show a spinner. The
editor chrome and placeholder already appear instantly, so these
animations only flashed a spinner and faded content in before the real
document hard-cut in. This is scoped entirely to
`apps/dotcom/client/src/tla/` — the tldraw SDK (`packages/editor`,
`packages/tldraw`) is unchanged, so the default `LoadingScreen`,
`DefaultSpinner`, and `.tl-loading` fade are untouched for SDK
consumers.

What changed:

- Set `LoadingScreen: null` on the shared tla `components` object, so
the file editor, local editor, and root-providers editor mount with no
SDK spinner or fade. The placeholder editor's now-redundant override was
folded away.
- The auth gate in `useUser` no longer renders a `<LoadingScreen>` with
a `<DefaultSpinner>`; it renders a blank editor surface while auth
loads.
- Removed the fade-in transitions from the `useIsReady` shroud (page and
file-content opacity fades) and deleted a dead `.spinner` fade rule.
Content now hard-cuts in once the editor is ready.

Relates to #9249

### Change type

- [x] `improvement`

### Test plan

1. Open a file on tldraw.com (and a local file at `/`): the editor and
its content appear with no fade-in and no spinner.
2. While signed-out/auth is loading, confirm no spinner shows — just a
blank editor surface.
3. Confirm the SDK examples app still shows the default loading spinner
and fade (SDK behavior unchanged).

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

### Code changes

| Section | LOC change |
| ------- | ---------- |
| Apps    | +10 / -37  |
In order to avoid hundreds of redundant `Image.decode()` calls when the
editor mounts, this PR deduplicates icon preloads by their underlying
resource. The `AssetUrlsProvider` effect preloads every icon URL, but
all ~324 icon URLs point at the same SVG sprite sheet and differ only by
their `#fragment` (e.g. `0_merged.svg#tool-arrow`). The fragment doesn't
change the resource, so the effect was creating one `Image` and
`decode()` per icon for what is really a single file. This was visible
as repeated, slow work in `asset-urls.tsx` during editor load. The
effect now strips the fragment and tracks preloaded resources in a
`Set`, so each unique resource is decoded once. Embed icons (separate
files) are unaffected and still preload individually.

### Change type

- [x] `improvement`

### Test plan

1. Load the editor and confirm icons render correctly (toolbar, menus,
style panel).
2. In the Performance panel, record editor load and confirm the icon
preload work in `asset-urls.tsx` no longer repeats per-icon.

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

### Release notes

- Reduce redundant icon preloading work on editor mount by deduplicating
sprite-sheet preloads.
#9316)

🙇‍♂️ Tested with Desktop + Wacom (correctly does not enter pen mode) and
iPad (correctly enters pen mode).

---

In order to stop desktop graphics tablets from hijacking the editor into
pen mode, this PR makes pen mode auto-enable only for stylus input that
looks like direct manipulation on the display (e.g. Apple Pencil on an
iPad or a Surface Pen on a touchscreen).

Previously, any `pointerdown` with `pointerType === 'pen'` flipped the
editor into pen mode, which then ignores touch/mouse input. That's
correct for a touchscreen stylus, but wrong for an indirect desktop
tablet (e.g. a Wacom Intuos) used alongside a mouse, where it would
unexpectedly disable the mouse.

We now distinguish direct from indirect pens using implicit pointer
capture: direct-manipulation pointers receive implicit capture on
`pointerdown`, so the canvas already holds capture before we call
`setPointerCapture` ourselves. Indirect tablet styluses do not. This
check is deliberately scoped to auto-entering pen mode — `isPen` still
drives pen-specific drawing and pressure handling for all pens, so an
indirect tablet stylus continues to draw with pressure; it just no
longer forces pen mode.

### Change type

- [x] `bugfix`

### Test plan

1. On a desktop with an indirect graphics tablet (e.g. Wacom Intuos),
draw with the stylus — the editor should keep responding to mouse and
touch input (pen mode stays off).
2. On an iPad with an Apple Pencil (or a touchscreen with a Surface
Pen), draw with the stylus — the editor should enter pen mode and ignore
palm/touch input as before.

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

### API changes

- Added optional `isPenDirect` field to `TLPointerEventInfo`, indicating
whether a pen event appears to be direct-display manipulation. Drawing
and pressure behavior remain driven by `isPen`.

### Code changes

| Section         | LOC change |
| --------------- | ---------- |
| Core code       | +41 / -3   |
| Tests           | +69 / -1   |
| Automated files | +1 / -0    |

### Release notes

- Fix desktop graphics tablets (e.g. Wacom) incorrectly switching the
editor into pen mode. Pen mode now auto-enables only for direct-display
stylus input such as Apple Pencil on iPad or Surface Pen on a
touchscreen.
…tion (#9352)

🙇‍♂️ The fractional indexing libraries we were using were doing silly
things. I was never really happy that we moved to an external lib for
this, it's a small thing and we benefit from just using the parts that
we need and improving those.

**What's changed?**

This PR vendors a trimmed, base-62-specialized version of
`fractional-indexing` and `jittered-fractional-indexing` (both CC0-1.0
public domain) into `packages/utils`, replacing the
`jittered-fractional-indexing` dependency. tldraw only ever uses the
default base-62 alphabet, so specializing away the `digits` parameter
lets us hoist per-call allocations and remove redundant work on hot
paths:

- `validateIndexKey` (run on every IndexKey validation) now validates
directly instead of generating and discarding a jittered key, which
previously ran the full key generator ~31 times per call.
- The jitter loop validates its two inputs once instead of re-validating
both endpoints on every bisection.
- `validateOrderKey` is allocation-free (no per-call `repeat`/`slice`),
and digit lookups use char-code arithmetic instead of scanning the
62-character alphabet.
- Jitter is reduced from 30 to 16 bits, which shortens every generated
key by ~3 characters and roughly halves per-key generation work.

**What prompted the change?**

Index keys are generated and validated constantly — on every shape
insert, reorder, and record validation — so this is a genuine hot path.
The dependency's `validateOrderKey` allocated a fresh 26-character
string on every call, and `validateIndexKey` was generating and throwing
away a fully jittered key just to check validity. Inlining the library
lets us cut that waste and drop a third-party dependency at the same
time.

**What do I need to know?**

The public API of `@tldraw/utils` is unchanged (`getIndices*`,
`getIndexBetween`, `validateIndexKey`, etc. keep their signatures); the
vendored core is internal. Reducing jitter from 30 to 16 bits keeps
collision probability below ~0.1% even for ~10 simultaneous inserts into
the same gap, and existing keys stay valid — only newly generated keys
are affected.

### Change type

- [x] `improvement`

### Test plan

- [x] Unit tests

The existing reordering and validation suites pass unchanged. A new
`fractionalIndexing.test.ts` pins byte-for-byte compatibility against
the upstream library's published vectors and adds ordering/validity
invariants across the full base-62 alphabet (integer carry/borrow,
jittered and non-jittered generators).

### Release notes

- Removed the `jittered-fractional-indexing` dependency and sped up
index key generation and validation. Newly generated index keys are
slightly shorter.

### Code changes

| Section        | LOC change |
| -------------- | ---------- |
| Core code      | +282 / -7  |
| Tests          | +138 / -0  |
| Config/tooling | +0 / -18   |
In order to reason about large Chrome DevTools performance traces
without loading tens to hundreds of MB of JSON into context, this PR
adds a `simplify-trace` agent skill that summarizes a trace into a
compact, few-KB markdown report. The skill ships a Node script
(`scripts/simplify-trace.mjs`) plus `SKILL.md` guidance on when and how
to use it.

The report surfaces the work taking too long or happening too often:
long tasks (main-thread jank, with offsets), hottest event types by self
time, most frequent event types, hottest JS functions (with `file:line`
from the embedded V8 CPU profile), and a self-time-by-category
breakdown. Opt-in sections cover a per-second main-thread timeline, a
network waterfall, and WebSocket lifecycle. `--window START-END` scopes
the whole report to a time slice, which is how you isolate a single
action in an otherwise idle "what happens when I do X" recording. The
script is read-only — it summarizes the trace and never modifies it.

### Change type

- [x] `other` (internal agent tooling)

### Test plan

1. Run the script on a Chrome DevTools trace: `node
skills/simplify-trace/scripts/simplify-trace.mjs <trace.json> --out
/tmp/report.md`
2. Confirm the markdown report includes the summary header, long tasks,
hottest events, frequent events, hottest JS functions, and categories.
3. Re-run with `--window START-END`, `--only timeline`, and `--list` and
confirm scoping and section selection work.

### Code changes

| Section        | LOC change |
| -------------- | ---------- |
| Config/tooling | +549 / -0  |

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@pull pull Bot locked and limited conversation to collaborators Jun 20, 2026
@pull pull Bot added the ⤵️ pull label Jun 20, 2026
@pull
pull Bot merged commit 09bf318 into code:main Jun 20, 2026
@pull
pull Bot had a problem deploying to bemo-canary June 20, 2026 03:13 Failure
@pull
pull Bot had a problem deploying to deploy-production June 20, 2026 03:13 Failure
@pull
pull Bot had a problem deploying to vsce publish June 20, 2026 03:13 Failure
@pull
pull Bot had a problem deploying to bemo-canary June 20, 2026 03:13 Failure
@pull
pull Bot had a problem deploying to deploy-staging June 20, 2026 03:13 Error
@pull
pull Bot had a problem deploying to deploy-staging June 20, 2026 03:13 Failure
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant