undo: - NULL/empty label no longer crosses to oakundo as a dangling 0x1 pointer (push, group_begin/end) — fixed SIGSEGV - group_abort now undoes each executed child in reverse order task: - create_project_import addrefs the borrowed project handle instead of freeing it under the async task — fixed UAF/SIGSEGV timeline: - toggle_enabled/delete_clips guard NULL+0 slices — fixed SIGABRT - BlockSplitCommand halves placed correctly (oaktimeline undosplit) - PreservingLinks / ripple remove / ripple delete-gaps commands self-prepare on first redo — fixes silent no-op split/ripple - trim_clips_to targets the block containing the point, not the track - delete_empty_tracks applies the live track removal - ripple facades no longer free borrowed track handles still referenced by commands — fixed UAF node: - project_add_node releases the factory handle — fixes per-call leak - inputs_from(recursive=0) matches direct feeders (BFS off-by-one) - group passthrough id/resolve treat two-stage string length as success - node_connect(_command) reject duplicate connects with E_STATE - folder_add_child enforces one-folder-per-node - value_split_to_tracks splits vector/color per component - set_context_position/expanded establish the first entry - node_get_flags on an empty box returns 0, not u64::MAX - footage_borrow addrefs its wrapper — fixes double-free render: - renderer_create rejects invalid pixel formats (real range check) - render_frame forwards renderer width/height to the ticket tests: repro #[ignore]s removed, bug-behavior assertions corrected, it_undo global-stack tests serialized with a shared lock
oaktimeline Rust crate (declaration draft, for review)
Status: declaration draft. Signatures + doc comments are the spec; every body is
todo!(). Not wired into any build. The crate template (FFI discipline, testing layers) followscrates/oakplugin/README.mdandcrates/oaknode/README.md.
Scope
Replaces the C++ oaktimeline module (src/timeline/src): timeline
markers and work areas, the timeline undo-command family (add/remove
tracks, place/trim/split blocks, ripple edits, slide, gap insertion),
and the shared Timeline namespace / utility helpers.
Public contract: include/timeline/*.h (error.h, displaymode.h,
marker.h, workarea.h, edit.h) — frozen, implemented verbatim by
src/ffi.rs. The timeline value handles (OakTimelineMarkerList,
OakTimelineWorkArea) and every edit command are exported through this
ABI only; consumers (the facade/app, the oaknode crate) never see the
internal Rust types.
Key architectural decisions (C++ → Rust mapping)
-
Domain modules mirror the C++ header files. The C++ module is a flat set of headers, not a deep class hierarchy, so the Rust crate keeps one module per C++ header family (
common,marker,workarea,undocommon,undotrack,undogeneral,undopointer,undoripple,undosplit,util).COVERAGE.mdmaps every C++ type to its Rust home; review that first. -
No C++
UndoCommandsubclass hierarchy. Following the oaknode crate decision (#4), each undo command is a plain Rust struct that exposesprepare()/redo()/undo()(alltodo!()here) and is surfaced to the world through the oakundo C ABI vtable (bridge::undo::oakundo_command_init, Rust callbacks asuserdata). Every command struct carries ato_command() -> CHandlefactory doc comment describing the wiring. -
Value types come from
oakcore-rs. Markers and work areas are built onRational/TimeRange, so the crate depends onoakcore-rs(crates/oakcore) exactly like oaknode does; no pixel/sample formats are involved here. -
All cross-module access goes through the C ABI. Per the project rule (no cross-module C++ member calls), timeline commands touch the node graph exclusively through the oaknode C ABI (
bridge::node), undo through the oakundo C ABI (bridge::undo), and XML/config through the oakcommon C ABI (bridge::common). The C++ internal helpersoakundo_capi::make_command_handle/oaknode_c_api::to_nativeare not replicated in Rust — their role is subsumed by vtable commands and by value handles treated as opaque. -
Handles.
handle.rsprovides the sharedRefBox/CHandlescaffolding (duplicated per crate, as in oaknode) withOAKTIMELINE_ABI_VERSION = 1. Borrowed handles into node-owned objects and owning handles created by*_createshare one box layout{ctx, addref, release, abi_version}.
Layout
src/
lib.rs crate doc + module map
error.rs error codes (mirrors include/timeline/error.h)
handle.rs refcounted-handle scaffolding (same pattern as node)
common.rs Timeline namespace (MovementMode/ThumbnailMode/
WaveformMode, EditToInfo) — timelinecommon.h
marker.rs TimelineMarker/MarkerList + 5 marker commands
workarea.rs TimelineWorkArea + 2 workarea commands
undocommon.rs node/block remove helpers + CHandleCommandWrapper
undotrack.rs track ripple/prepend/insert-after/replace commands
undogeneral.rs resize/media-in/add/remove-track/transition/gap/
enable-disable/insert-gaps/default-transition commands
undopointer.rs BlockTrimCommand/TrackSlideCommand/TrackPlaceBlockCommand
undoripple.rs ripple remove-area / ripple-tool / delete-gaps commands
undosplit.rs BlockSplitCommand/BlockSplitPreservingLinksCommand/
TrackSplitAtTimeCommand
util.rs timelineutil.h inline helpers (rat_nd, same_*,
free_detached_handle, block/track queries)
bridge/ C ABI imports: node.rs, undo.rs, common.rs
ffi.rs include/timeline/*.h export layer
tests/ contract + golden tests (see README test section)
Hard rules for the implementer
- Every
extern "C"body goes throughhandle::guard*; no panic crosses FFI. - Timeline objects never outlive their owning node; borrowed handles are created under a guard that owns the node reference.
- Behavior parity with C++ is proven by the C ABI test-suite
(
src/timeline/tests, unchanged) plus the golden tests intests/(XML save/load formats captured verbatim fromsrc/timeline/src/timelinemarker.cpp/timelineworkarea.cpp). - Where C++ behavior is genuinely load-bearing but ugly (e.g. marker
list kept sorted by time, ripple's compensation gap rules), port the
behavior, not the aesthetics; leave a
// CPP-PARITY:comment with the C++ file:line.
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.