Programmatic changes in tlDraw without affecting undo/redo stack

While working with the tlDraw framework for infinite canvases, you may want to make programmatic changes to the content in the editor without the user being aware of them. That means that some of your modifications shouldn’t appear in the undo stack.

If you let your programmatic changes register on the undo stack, often, each user initiated action could result in multiple programmatic changes behind the scenes. Meaning if the user tries to undo their action, they have to undo several other changes as well. Changes that they didn’t event realise occurred.

This article looks at handling that.

An example problem

Imagine you create a flowchart app that allows the user to drag flowchart bubbles around. When they drag a bubble close to another bubble, the app automatically connects them with a line.

The problem

In this example, the user might drag a bubble (One user action), but upon releasing their mouse, the app automatically draws a line between the bubbles, adds a label to the line, and changes the colour of the bubbles (Three programmatic changes).

By default, this creates four undo entries. one for the bubble colour changes, one for the label, one for the line, and one for the bubble drag. But as far as the user is aware, they only did one action, so if they choose to undo, they expect to only have to undo once.

Problem code

If you do the below, it will add an entry onto the undo stack for the colour changes.

editor.updateShapes([
    {
        id: shapeId1,
        type: 'bubble',
        props: {
            color: 'green'
        }
    },
    {
        id: shapeId2,
        type: 'bubble',
        props: {
            color: 'green'
        }
    }
]);

The solution

There are a couple of ways to handle this and your approach should change depending on how your program works.

Squash or discard

One way to handle this would be to squash all of the changes into one undo entry. This means that when the user selects undo once, tlDraw will undo the colour change, undo the label, undo the line, and undo the bubble drag as if they were all one action. This may be the option you want, but not necessarily in this case.

In our instance, we have likely programmed the app to programmatically reassess lines, labels, and colours whenever anything changes, so we don’t actually need to undo those. Instead, we undo the user’s bubble drag, and everything else will recalculate automatically – But bare in mind, your app needs to be built to work like this (ie. after every tldraw change, it recalculates and adds/removes what’s needed).

Essentially, in this example, we can tell tlDraw’s undo stack to ignore any of the changes we did programmatically. To do this, when we make the changes, we pass a flag to the method to indicate it is ephemeral.

Solution code

editor.updateShapes([
    {
        id: shapeId1,
        type: 'bubble',
        props: {
            color: 'green'
        }
    },
    {
        id: shapeId2,
        type: 'bubble',
        props: {
            color: 'green'
        }
    }
], {
    ephemeral: true,
});

That’s it!

For the official documentation on this, refer to the tlDraw documentation for TLCommandHistoryOptions (Which is the object we’ve passed in above), and the editor.updateShapes method it’s passed into.

There are also two other history options we haven’t directly address here (squashing, and preservesRedoStack). But let’s leave them for another article.

Thanks…

I also dissect and speculate on design and development.
Digging into subtle details and implications, and exploring broad perspectives and potential paradigm shifts.
Check out my conceptual articles on Substack or find my latest below.


You can also find me on Threads, Bluesky, Mastodon, or X for more diverse posts about ongoing projects.

My latest articles

Focal point blocking for XR media

Planning out a linear VR experience requires thinking about where the viewers attention might be. Thinking about the focal points…

Designing immersive experiences

In traditional cinema, TV, or even the more modern phone screen, there’s limited screen real-estate. But removing that limitation creates a design problem…

The future is not prompt engineered

Let’s not pretend the importance of prompt engineering is ubiquitous. The most prevalent power of generative AI is in the way it adapts to us, not the other way around…

Author:

Date:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.