In my own Clojure projects I now even included that as part of the app. I literally have a header (I'm working on webapps, with Datastar btw) with buttons to pause/rewind/forward/fast-forward through my application's states (and I can also move back/forward in time from the REPL ofc). And I can then resume from any past state/inspect any past state from the REPL, save any state, etc.
I used to do it in the past with re-frame and events replayed but it was, IMO, a bit complicated for my needs. I now much prefer to have it all on the server-side and I simplified everything by using SSE / datastar (YMMV: for more complex needs, SSE may be insufficient).
Everything is immutable/functional except for one atom that contains the app's state. And that one is basically both a DB and a DB of all previous states.
I haven't done the full undo/redo as a tree yet (say like Emacs' undo/redo does) but it's been modeled to allow it. So I'll add that "navigation into past app state as a tree" (i.e. when I when backward then forked the state) visualization at some point.
Immutability / functional code / determinism / the passage of time are all fascinating things to me.
And once you've got the ability to walk through time as part of you language/REPL/app, testing and debugging becomes oh-so-much easier.
P.S: and the LLM is surprisingly good at making sense of out how I modeled the passage of time and the ability to walk through time as part of the app.