Skip to main content

record_dispatched_action

Function record_dispatched_action 

pub fn record_dispatched_action<S>(
    state: &mut State,
    root: *mut dyn Widget,
    node_map: &HashMap<NodeId, *mut dyn Widget, S>,
    author: &UserId,
) -> bool
where S: BuildHasher,
Expand description

Turn the actions a widget dispatched during the last event into exactly one undo boundary. Returns whether an entry was recorded.

The counterpart of shortcut for the other direction: the single place “an edit became an undo boundary” is written down, so a widget behaves identically under the live host (App) and under the headless FrameHarness. Both call this once per tick.

It matters that there is only one of these, because there are two separate ways to get it wrong and both are silent:

  • Not draining at all. take_committed_action is rising-edge, so a host that never reads it records nothing — an app whose edits all travel through tilo_actions then has an empty undo history no matter how much the user changed. The driver did exactly this, which made undo unobservable to every headless test of such an app.

  • Draining anonymously. Undo is author-scoped: an entry with no author is skipped by the local user’s own Ctrl+Z, which then reverts an older entry instead. So the boundary must be attributed, and to the same identity State::undo_as is asked about.

  • Draining only the root. This is the third host→widget channel of its kind, and it was the only one that read a single widget: no container in the toolkit forwards take_committed_action, so an app that put its editor inside a Row, a Column, or a NavShell lost every boundary with no error anywhere. Like UndoStatus and UndoRequest, this sweeps node_map — every laid-out widget registers itself there — so a widget is reached at any depth.

Every bracket found is drained, even when the actions turned out to change nothing: the rising edge must be cleared, or a later unrelated edit would be bracketed from a stale point and undone along with it.

One tick is one boundary. When several widgets dispatched in the same tick, the entry spans from the earliest of their brackets — the one the others descend from — so a single Ctrl+Z reverts the tick rather than peeling it apart in an order the user never performed.

§Safety

root and the node_map pointers must point at widgets that are alive for the call — the invariant every node_map sweep in App and FrameHarness already relies on. Exactly one &mut is live at a time, so a widget that is both the root and registered in the map is safe (and yields its bracket once, because reading clears it).