Skip to content

How an edit flows

Every user edit is one Tauri command and one undo step. Taking “move a region” as an example:

sequenceDiagram
  participant UI as React component
  participant API as api.ts act()
  participant C as commands/regions.rs move_region
  participant S as Session
  participant E as Engine (audio thread)
  UI->>API: act("move_region", { id, start, track })
  API->>C: invoke (args camelCase in JS, snake_case in Rust)
  C->>S: edit(): checkpoint("Move Region")
  C->>S: move_region(id, start, track)
  S->>S: update Project, regions_dsp(track)
  S->>E: Cmd::Regions(track, new RegionDsp list)
  C->>S: frozen_guard()
  C-->>API: Snapshot (project.for_ui(), undo label, ...)
  API->>API: setState({ snap }) and toast if refused
  E-->>S: Garbage::Regions(old) (dropped in idle)

The pieces:

  • act(cmd, args) in src/api.ts invokes a command that returns a Snapshot (or a Made, which wraps one) and stores it in the global store. Errors become a toast. actMade returns the ids an edit created, so the caller can select them. call is the plain form for commands that return other data.

  • Argument names: JavaScript passes camelCase keys (fadeIn), which Tauri maps to the Rust parameter fade_in. Fields inside a struct argument (a TrackPatch, a Region) keep the serde names, which are snake_case.

  • Command wrappers in src-tauri/src/commands.rs:

    Wrapper Undo step Returns
    snap(f) no Snapshot
    try_snap(f) no Result<Snapshot>
    edit(label, key, plugins, f) yes, then frozen_guard Snapshot with refused set when the edit touched a frozen track
    try_edit(label, key, plugins, f) yes, then frozen_guard Result<Snapshot>
    edit_made(label, plugins, f) yes, then frozen_guard Result<Made> (snapshot plus new ids)

    key coalesces a burst of edits into one step (see Undo); plugins makes the checkpoint capture plug-in states, for edits that remove plug-ins.

  • Not undoable on purpose: mute, solo, track on/off, record enable and input monitoring (performance switches, as in Logic; update_track gives them no label), transport, cycle and Autopunch range changes, track selection, the metronome and count-in switches.

  • The Snapshot (commands.rs) carries the project without plug-in state blobs (Project::for_ui), the selected track, project path, saved/dirty flags, audio status, MIDI inputs, open plug-in editors, the MIDI target name, undo/redo labels and the PDC report. Session::idle also emits a fresh snapshot as the project event when editors open or close, automation was written, or a plug-in’s latency changed.

  • Ticks are separate and much lighter (see The frontend).