- real.rs rewritten over module Rust APIs (Arc<Mutex<Project>> + NodeId; the addref handle dance and renderer boxes are gone); AppEngine trait and all panels untouched - new app assembly layers: graphops (project/timeline/edit primitives), effectchain (chain composition with undo groups), renderops (montage build + ticket render + ExportTask export), library via oakstorage directly - module-side safe API additions: oakundo global value-semantic push/undo/redo + from_closures, oakstorage project_arc_of - deleted: src/oakui/ffi.rs, src/oakui/host_syms.rs, the dylib link config in build.rs (only the gpui IOSurface framework link remains) - the binary carries zero liboakengine references (otool/nm verified); 101 app tests green incl. the real-render and full-res e2e tests - behavior improvements for free: sequences land in the project graph (the facade scratch-project deviation is gone), footage drops take one undo record, effect remove/reorder undo restores edges
oakundo Rust crate
Status: implemented. Ports the C++ oakundo module (
src/undo/src) to Rust behind its frozen C ABI (include/undo/*.h). Template followscrates/oakplugin.
Scope
Replaces the C++ oakundo module (src/undo/src): undoable commands
and the undo/redo history stack. Public contract: include/undo/*.h
(3 headers: error.h, undocommand.h, undostack.h) — frozen,
implemented verbatim by src/ffi.rs.
Architectural decisions
- Vtable-command pattern is the centerpiece. In C++ other modules
subclass
olive::UndoCommand(redo()/undo()overrides) and plug themselves in polymorphically. Rust has no inheritance, so the C ABI already models exactly this withOakUndoCommandVtable { redo, undo, free_fn }plus a caller-owneduserdatapointer. The safe layer's [undocommand::CommandKind] is the direct analog: either a caller-defined vtable command (function pointers + userdata) or a [undocommand::MultiUndoCommand] composite. Domain logic dispatches on the vtable the same way the C++ virtual dispatch does. - Modified-state callbacks are intentionally not part of the C ABI.
The C++
UndoCommand::redo_and_set_modifiedpair records/restores a project dirty flag viastd::functionaccessors. The public headers expose none of this; the stack drives state viadone_on the safe type instead, and the flag callbacks are left as a documented future extension. UndoStackstate machine is modeled directly on the C++: two deques —commands_(done, oldest at front) andundone_commands_(most-recently-undone at front);pushclears any redoable tail, executes redo, and drops the oldest when the cap (200) is exceeded;jumpclamps and walks viaundo/redo. The fresh stack holds a single "New/Open Project" empty command socan_undois false at the bottom (perundostack.cpp).- No merge semantics.
include/undo/*.handsrc/undo/src/*define nomerge_with/can_merge; commands are never coalesced. Tests reflect this (no merge tests).
Layout
src/
lib.rs crate doc + module map
error.rs error codes (include/undo/error.h)
handle.rs refcounted-handle scaffolding (OAKUNDO_ABI_VERSION=1)
undocommand.rs UndoCommand / vtable command / MultiUndoCommand
undostack.rs UndoStack + empty bottom command
ffi.rs export layer (one submodule per public header)
tests/ contract tests per module
error.h exports macros only and is folded into ffi.rs's preamble
(no own submodule), matching the codec crate convention.
Dependency policy
Prefer mature third-party crates (MIT/Apache-2.0/BSD, GPL-compatible) over hand-rolling; register each addition (name + reason) here. Large existing C++ libraries (OTIO, OCIO, OIIO, FFmpeg) are NEVER rewritten — they are consumed through their C ABI / bridge layers.