Windows: tooling/package/bundle-dylibs-windows.sh collects the MSYS2
runtime DLLs (libstdc++, libgcc, OpenColorIO, ...) with ntldd -R,
iterated to a fixpoint over freshly copied DLLs; a packager resources
glob places them next to the executables in the NSIS installer.
macOS: tooling/package/bundle-dylibs-macos.sh copies every non-system
dylib otool reports into Contents/Frameworks, rewrites the install
names to @executable_path/../Frameworks to a fixpoint, and ad-hoc
re-signs every modified Mach-O (rewriting invalidates the seal).
The CD package version no longer comes from the git tag: the root
Cargo.toml gains [workspace.package] version = "0.5.0", the oak
package inherits it (version.workspace = true — which cargo-packager
also picks up), and the Linux container packaging parses that field.
docs/build.md + docs/zh/build.md rewritten for the Rust workspace:
project-built FFmpeg 8.1 (.cargo/config.toml presets FFMPEG_DIR),
vendored static OCIO on Linux/macOS vs MSYS2 dynamic OCIO on Windows
(with the OCIO_INSTALL_DIR/OCIO_RS_LINK env), the Windows GNU toolchain
requirements (MSYS2 Rust, RUSTFLAGS=-C link-args=-lmsvcrt for the
mingw-w64 _assert forwarding, unset INCLUDE/LIB), Linux audio dev
packages and xvfb headless testing, container packaging, and a
troubleshooting section. The macOS-only guides gain a deprecation
pointer. Also correct two stale comments in tooling/install-deps.sh
(FFmpeg is built by tooling/ffmpeg/build-ffmpeg.sh, not by cargo).
The fixture plugin binary was copied into Contents/Linux-x86-64 under
the extension-less name "plugin" on every non-macOS platform. On
Windows the host never loads it: LoadLibrary appends .dll to
extension-less module names, so the scan found the bundle but produced
no plugin — and because the (passing) draw-overlay test scans first,
the path dedupe then hid the failure from the lifecycle test, which
died with "interact variant instance: NotFound". Use Contents/Win64
and plugin.dll on Windows in both bundle assembly sites.
oakrender/build.rs: -Wl,-export_dynamic is the macOS spelling. Since
Rust 1.90 x86_64-unknown-linux-gnu links with rust-lld by default, and
lld parses the single-dash form as '-e xport_dynamic', every oakrender
integration test binary was linked with NO entry point and died with
SIGSEGV inside ld.so's dl_main (jumping to the image base) before
printing anything — the copier_test CI failure. Emit the flag on macOS
only.
oaknode timeformat: value_localtime_flag_routes_to_localtime_r called
localtime_r/gmtime_r directly, which do not exist on Windows. Factor
the cfg-gated FFI (localtime_r/gmtime_r vs _localtime64_s/_gmtime64_s)
into break_down_time() and use it from both value() and the test.
oakcodec: gate find_ffmpeg_searches_path to unix (chmod 0755 + shebang
fixture) and make find_ffmpeg_missing_returns_empty assert absoluteness
instead of a '/' prefix so the tests compile and pass on Windows.
ci (Windows): export RUSTFLAGS=-C link-args=-lmsvcrt in the build and
test steps. mingw-w64 (Nov 2025) forwards _assert to __msvcrt_assert
inside libmingwex.a, and rustc's link order leaves -lmingwex last, so
binaries that pull _assert.o (oakcommon's real_ocio test) fail to link;
a trailing -lmsvcrt re-scans the CRT import lib afterwards.
ci (Linux): copier_test dies inside ld.so before printing anything.
Replace the LD_DEBUG probe with stronger forensics: exported dynsyms
(interposition suspects), strace tail, valgrind tail, and siginfo
(si_code/si_addr) from the gdb run.
oakaudio: drop cpal's `asio` feature. asio-sys needs the proprietary
Steinberg ASIO SDK at link time (undefined ASIOGetSamplePosition etc.
on the GNU toolchain); WASAPI remains the Windows backend.
ci: the failure-only gdb step passed test args without --args, so gdb
treated --nocapture as a core file. Also collect loader-stage evidence
for the copier_test dl_main SIGSEGV: IRELATIVE reloc count, LD_DEBUG
tail, full backtrace and registers.
- localtime_s/gmtime_s are MinGW header inlines, not symbols — link
_localtime64_s/_gmtime64_s
- copier_test also segfaults only on the Linux runner; add it to the
on-failure gdb backtrace
- timeformat node: localtime_s/gmtime_s (reversed args, 64-bit time_t)
on Windows — MinGW has no localtime_r/gmtime_r
- the multithread suite test passed non-NUL-terminated property names
(str::as_ptr) to the C property suite — UB that resolved to garbage
lookups on the CI runner
- libsnappy off on the MinGW FFmpeg build (only feeds the hap encoder;
its pkg-config entry does not reach the static link)
- FFmpeg for Windows no longer enables libopenh264 (redundant with the
native h264 decoder + x264 encoder; its MinGW packaging does not
satisfy the static link — Wels* undefined references)
- suites_test's question-type call passed a 3-placeholder format with
one variadic arg — UB that vsnprintf turns into a SIGSEGV on glibc
(masked on macOS); use a placeholder-free format
- oakffmpeg-link forwards pkg-config --static --libs verbatim; FFmpeg's
.pc files can list -ldl via external deps, and MinGW has no libdl
- suites_test segfaults on the Linux runner too; run both plugin test
binaries under gdb on test failure
Tests (and any host that shuts down then rescans) can hold instances of
a PREVIOUS plugin generation; their entry points dangle after dlclose,
so the next shutdown's destroyInstance notification jumped into
unmapped memory (SIGSEGV on Linux; masked on macOS). Plugin gains an
flag set by unload_all before dlclose; call_action/call_entry
fail fast instead of calling into freed code.
The crate's build.rs unconditionally adds the MSVC + Windows SDK
include dirs on Windows (for MSVC hosts); on the GNU toolchain that
breaks the bridge compile with MSVC-only headers. The runner's job
hook re-exports INCLUDE/LIB per step, so the in-step unset did not
help — patch the extracted build.rs instead (both the env-var failure
modes are now documented in the step comment).
- the warp runner's job hook re-exports MSVC INCLUDE/LIB per step, so
the GITHUB_ENV clear did not stick — unset in the Build/Test steps
themselves
- node_e2e_test segfaults only on the Linux runner; rerun the binary
under gdb on failure to capture the native stack
Each distro package builds inside that distro's container so declared
dependencies always resolve to native names: hand-rolled deb via
dpkg-shlibdeps + dpkg-deb, rpm via rpmbuild's auto-requires, Arch via
makepkg (non-root builder user). git/curl install before checkout
(container jobs). AppImage keeps cargo-packager on the Ubuntu runner.
The release gates on all four package jobs plus macOS/Windows.
- the Windows runner image exports MSVC's INCLUDE/LIB; cc-rs was
appending the MSVC SDK headers to MinGW compiles (vcruntime.h not
found)
- oak-worker handshake test helper advertised the input pool's total
byte size as per-slot data bytes (macOS tolerated the oversized
attach; Linux correctly rejects it)
- hw/sw decode comparison tolerance 0.05 -> 0.08 (VideoToolbox's
YUV->RGB legitimately differs by ~1 LSB of intermediate depth)
- the vendored OCIO source needs MSVC-only constructs (wide-path
ifstream); MSYS2's mingw build of the exact 2.5.2 the bridge targets
is the sane Windows path — DLLs get packaged next to the binaries
- oak-worker handshake test prints the error response on failure
(CI-only attach failure needs the message)
Each dimension of a parametric param renders as a CurveEditor in the
inspector (bezier handles map to the Hermite slopes of the host curve
model; edits serialize back through the JSON mirror — undoable and
project-persisted). The engine re-sync skips in-progress drags and
identical curves so the per-render sync neither steals gestures nor
loops. Also: physical-memory probe for the worker-count policy on
Windows (GlobalMemoryStatusEx).
- rust-toolchain sets CARGO_HOME to the Windows userprofile path while
the msys2 shell's HOME is elsewhere — the yaml-cpp patch targeted an
empty directory and the assertion ls failed
- examples/screenshot.rs uses the macOS-only VisualTestAppContext; its
items are now cfg-gated with a non-macOS stub main so workspace test
builds pass on Linux/Windows
- ValueType::Parametric; the node input carries the whole curve set as
NodeValue::Text(JSON) so undo and project serialization come for free
- translation pass builds the input with the default-curve JSON and the
dimension/range/ui-colour properties
- edits flow both ways: node input (UI) -> curves_from_json ->
set_ofx(Parametric) on the instance; plugin-side Set/Add/Delete ->
notify_instance_changed -> JSON written back to the input (undoable)
- screenshot example: gate the macOS-only offscreen capture items so
the workspace tests build on Linux/Windows
- oakplugin host: Win32 LoadLibraryExW/GetProcAddress/FreeLibrary
backend (LOAD_WITH_ALTERED_SEARCH_PATH so bundle-sibling DLLs
resolve), same dl_open/dl_sym/dlclose surface — the POSIX path is
untouched; OFX hosts now compile on Windows
- proxymanager: PATH split via std::env::split_paths (Windows ';'),
ffmpeg.exe name, Windows candidate locations; split logic unit
tested
SharedMemoryRegion gains a Win32 backend behind the unchanged public
API: Local\OakShm<key> names, OpenFileMapping for attach, VirtualQuery
for the size check, UnmapViewOfFile/CloseHandle for teardown. Semantic
differences from POSIX are documented: unlink_key is a no-op (the
kernel destroys the object with the last handle, so crashed owners
self-heal) and Create on a live name fails instead of replacing.
Windows CI builds the workspace again.
On a fresh runner registry/src has no hash subdir yet, so the unpack
glob never expanded and the step exited 2; derive it from the cache
dir and assert the patched file exists at the end.
Parametric (curve/LUT) parameters: ParamValue::Parametric holds one
ordered control-point curve per dimension (identity default over the
declared range), evaluated as piecewise cubic Hermite with auto
(centered-difference) slopes; the full suite — evaluate / count / get /
set / add / delete / delete-all — with the spec's error codes, descriptor
defaults copied to instances, and instanceChanged notifications on
edits. paramDefine accepts OfxParamTypeParametric; the dimension/range
and UI-colour properties round-trip. 148/148 real plugins discovered,
135 registered (one more than before: the parametric-suite consumer).
The first processor build has no prior state to be stale against; the
spurious bump made the next cpu_frame call drop the freshly cached
image (playback_display_tracks_the_playhead regression).
- cargo fetch does not extract sources; the yaml-cpp <cstdint> patch
now untars the .crate into the registry src dir first (the glob
found nothing and the step failed with exit 2)
- oakaudio: the watchdog-wrapped audio test called Self::... from a
free-function test module (compile error in lib test)
- Linux: libxkbcommon-x11-dev for the gpui X11 client link
- Windows: patch <cstdint> into the vendored yaml-cpp (a cached cmake
configure ignores CXXFLAGS; the patch is idempotent and runs after
cargo fetch)
- macOS: the hw-decode test skips its VideoToolbox engagement
assertions on hosts where VT cannot initialize (headless/virtualized
runners) instead of failing
- display color management: the display ICC (system or custom) is
applied to viewer frames at present time (F32 in place, or in place
on the BGRA staging copy with the R/B swizzle baked into the OCIO
chain); preferences get a Color section (mode + custom ICC file); on
macOS the Metal layer is tagged with the display colorspace when
self-managing so ColorSync passes pixels through (no double
correction); frame caches track the transform generation so a mode
or profile change drops stale pixels
dpkg-shlibdeps over the three shipped binaries resolves every NEEDED
library to exact build-distro package names (FFmpeg/OCIO are static so
only base-OS packages appear) and rewrites the deb's Depends. Distros
with divergent package names (openKylin) get their own build instead
of a wrong-name dependency list.
Static FFmpeg + static OCIO leave only base-OS libraries; the audit
step prints objdump NEEDED for each packaged binary so any accidental
dynamic dependency (and any distro-specific package-name surface) is
visible in the build log.
- OCIO_RS_LINK=static everywhere: the vendored OCIO is linked into the
binaries statically — the package carries no OCIO dependency
- Linux: libasound2-dev (alsa-sys), libpulse-dev, libsndfile1-dev —
the full audio dev set
- Windows: -include cstdint for the vendored yaml-cpp (pre-GCC-13
transitive includes)
- Windows: install MSYS2's own Rust (x86_64-pc-windows-gnu host); the
rustup MSVC toolchain is not on the msys2 shell's PATH and the MSVC
linker rejects the Unix-style link args the build scripts emit
- Linux: libpipewire-0.3-dev + libspa-0.2-dev for libspa-sys (gpui's
Linux screen-capture/audio stack)
The distro OCIO is too old for the bridge's API floor where it matters
(Ubuntu 24.04 ships 2.1; the bridge uses 2.4+ APIs), and version drift
across platforms is a support hazard — enable ocio-rs' bundled feature
and drop the OCIO_INSTALL_DIR/system-package wiring from CI and CD so
Linux, macOS and Windows all build the same vendored OCIO. cmake/make/
diffutils added where the runners lack them (Windows FFmpeg build needs
make + cmp).
- Homebrew renamed libtheora->theora and libwebp->webp; the old names
no longer resolve, failing the macOS dependency step
- retry the MSYS2 pacman install (3 attempts, --needed resumes): CI
mirrors stall mid-download ("Operation too slow") often enough to
matter
- serializer resolves node types through the factory's dynamic
(runtime-registered OpenFX) entries, so a project carrying plugin
nodes loads again (was: "unknown node type"); covered by a new
CI-gated round-trip test driving the real fixture plugin
- built-in effect nodes expose their inputs as inspector parameters
like the C++ parameter editor: localized input names from the
behavior, combo option tables via the new
NodeBehavior::input_combo_strings (16 nodes, string-for-string from
the C++ set_combo_box_strings), connection/data inputs excluded
- effect library: live drag-and-drop — onto the inspector's effect
stack (lands at the indicator position) and onto the node editor
canvas (creates the node at the drop point); double-click still
appends to the selected clip
- inspector parameter controls are no longer recreated per render
(gpui stack view caches them per effect), so sliders drag and
checkboxes click; the view observes the engine and silently re-syncs
values (undo/redo land on the widgets)
- timeline: left-press selects clips (plain/keep-multi/Ctrl-Cmd
toggle); clip moves clamp the shared delta so no clip of a linked
group lands before frame 0 instead of failing with "invalid move
target"
- oakplugin: createInstance-rejected instances skip the destroyInstance
notification (the plugin never owned them); vendor-suite fetchSuite
misses moved behind OAK_OFX_TRACE; the worker logs the discovered/
registered plugin counts
- CI: the OFX probe step also runs the serialization round-trip test
- gpui submodule: params view caching, clip click-select, library
drag payload, graph_position_at
- new minimal C OFX plugin fixture (ci_test_plugin.c) compiled into a
real .ofx.bundle by build_fixture.sh; the CI step points
OFX_PLUGIN_PATH at it and asserts the scan_probe example discovers
AND registers it (Linux/macOS)
- host bundle binary search now also covers the OFX-standard Win64
platform directory
- cd.yml: restore the Windows NSIS packaging job (obsolete oakengine
cdylib prebuild dropped) and repair the job indentation that had
silently detached the macos/release jobs from the jobs: map;
releases now gate on all three platforms
- save() writes <config>/config.toml (atomic temp+rename), flat keys
at the top level and group/sub keys as [group] tables, values as
native TOML int/float/bool/string (non-finite doubles degrade to
strings and restore via the declared type)
- load() prefers config.toml; a legacy config.ini (C++ or pre-TOML
Rust builds) is read once and immediately re-persisted as TOML; the
INI file is left in place; a corrupt TOML is reported, never
silently discarded
- cd.yml: drop a stale oakengine comment (the crate is retired)
- NLE track growth is now a display concern: video/subtitle track
lists render reversed (a new track lands on top), audio lists render
in order (a new track lands at the bottom); the graph list always
appends. Track-add undo removes THIS track by id instead of blindly
removing the last one
- add_track returns the actual index of the new track (diffed against
the pre-command list) instead of assuming append-at-end
- status bar proxy segment reflects the real Use Proxy Media switch
instead of a static "Proxy: Off"
- proxy transcode PROGRESS events no longer invalidate the rendered
frame cache on every tick (only completion does) — progress updates
used to keep the playback cache permanently cold while generating
- effect library: live search box (name/type-id substring), Built-in
group header, and the addable-effects table is sorted alphabetically
(built-ins first, then OFX sub-category groups)
Real openfx-misc/CImg/Shadertoy bundles (148 plugins at
/Library/OFX/Plugins) all failed to load before; every failure was
silent. Root causes found one by one with a probe example + lldb:
- property suite rejected propSet on undefined properties and
propGetDimension on empty ones, and disallowed the index==size
append — OFX semantics are create-on-set and appendable dimensions
(this alone failed every plugin's describe)
- host property set missed the mandatory OfxPropType/OfxPropAPIVersion
and the capability props ofxs' fetchHostDescription reads with
throwOnFailure=true (IsBackground, TemporalClipAccess, MaxPages,
PageRowColumnCount, host SupportedContexts, ...) — one missing prop
aborted the read chain and left a half-initialised host description,
which made every temporal plugin refuse to load
- MultiThreadSuiteV1 lacked the five mutex functions (the plugin reads
past the short table — UB); implemented as a real counting-semaphore
registry
- the OfxHost struct was a stack local; ofxs keeps the POINTER past
setHost, so describe/render-time fetchSuite calls dereferenced a
dangling stack address (bus error once plugins actually loaded) —
the struct is now a leaked process global
- General is a standard OFX context and is no longer filtered out
(Roto/AppendClip/STMap declare only it)
- every scan/load/describe early-out now logs its reason; suite entry
points report non-OK statuses with caller location under
OAK_OFX_TRACE
- examples/scan_probe.rs: scans the real plugin dirs and prints
discovered/registered counts (also usable from CI)
Result: 148/148 plugins discovered, 134 registered as node types (the
remaining 14 need vendor suites — Vegas stereoscopic etc. — and are
logged, not silent)
- string tables live in assets/i18n/<lang>.yaml, loaded at runtime
(user pack dir ~/.oak/i18n, app bundle Resources/i18n, dev checkout)
with the compiled-in English/Chinese tables as fallback
- new status.proxy.on/off keys; inspector.params copy no longer says
"placeholder"; effect_library.group.builtin key
- bundle the packs as cargo-bundle resources; dev profile dep opt-level
dropped to 1 for faster iteration builds
- Thumbnails never appeared because the PNG was written to a .part
file with format inferred from the extension (always failing); the
writer now uses an explicit PNG encoder, and an e2e test proves the
pipeline yields real files.
- Mock engine: the CardSelected -> SelectionChanged echo no longer
re-expands a card the same click just collapsed.
- Dragging a clip moves its linked audio/video partners by the same
frame offset in a single undo entry; the dragged clip may change
tracks while partners keep theirs.
- Root view paints the near-black base so dock gaps match.
- Waveform trace follows the deep-green design color.
- actions.rs: reset the shortcut overrides in
save_writes_only_entries_that_differ_from_default (pre-existing
flake: a leaked override from a previous test poisoned the shared
lock under parallel test order).
- With a clip selected, the node editor shows that clip's context
chain (footage -> effects -> clip) instead of the global graph; the
clip's node is highlighted. No selection keeps the full graph.
- Node clicks in the graph select the node and expand/highlight the
matching effect card in the inspector; clicking an inspector card
highlights the node in the graph (single source of truth: the
engine's selected_graph_node).
- The Window menu lists every panel, checks the open ones, and toggles
visibility on click (closed panels reopen at their last dock target,
falling back to the default group) — a panel closed by accident
(e.g. the inspector) is one menu click away again. The menu refreshes
on dock structure events.
- OFX plugin scanning now covers the full standard location set:
per-user (~/.OFX/Plugins, ~/.local/share, ~/Library/OFX/Plugins on
macOS), system-level (/Library/OFX/Plugins, /usr/OFX/Plugins,
/usr/local, %ProgramFiles%\Common Files\OFX\Plugins), app-relative,
and the OFX_PLUGIN_PATH environment variable.
TrackPlaceBlockCommand::redo now homes the block's in point to the
placement target (capturing the original for undo): the Rust block
stores its position on the block, so a fresh clip that never had its
in point set always rendered at the timeline zero — the 'drops always
land at zero' bug. The original in point is captured on the first redo
and restored on undo, keeping the sync re-place round-trip exact. The
A/V drop test now asserts the clip lands at the drop frame.
- Footage drops no longer clamp to the sequence length — that clamp
squashed every drop on an empty or short timeline to frame zero.
Dropping past the end now extends the timeline, so the clip lands
where it is released.
- While dragging, a translucent ghost (35% opacity, accent border)
previews the resolved track, start frame and footage length at the
cursor; a new AppEngine::footage_length_frames (probed duration x
frame rate) feeds its extent, with a mock implementation for demo
mode.
- Main-process interact instances for the selected OFX effect card
(create on selection change, describe, destroy on deselect/close),
coexisting with the render-worker plugin instances per the OFX
multi-instance model.
- Program viewer composites the interact's overlay: draw into a GL
FBO via gl_bridge, read back, straight-alpha 'over' composite onto
the displayed frame; cached and only re-rendered on frame/time/
viewport/instance change or plugin redraw requests.
- Event forwarding: picture-area pointer maps through the contain-fit
letterbox inverse to OFX pen coordinates (pen_motion/down/up);
Keystroke to OFX key symbols (ASCII, navigation, F1-F35) for
key_down/up; a 50ms idle pump; global shortcut consumption keeps
precedence.
- e2e with the real test plugin: lifecycle marker assertions, pen/key
event records, and macOS GL overlay compositing verified (265 tests
green incl. gpui_widgets viewer suite).