- 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
- 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)
- 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).
- Right-clicking an unselected clip selects it first (C++ parity) —
this is what made Cut/Delete appear to do nothing.
- Cut/Copy/Paste clipboard: clipboard_copy/cut/paste on the engine,
clipboard clips keep footage/range/speed/track kind and stay linked
in the pasted group; paste lands at the playhead as one undo entry.
- Dropping a video-with-audio footage places the video clip plus a
linked audio clip at the same range in ONE 'Add Clip' undo entry.
- Add Video/Audio Track buttons in the timeline toolbar and the track
header context menu; new sequences start with 2 video + 2 audio
tracks (not an undoable edit).
- oaknode Graph::add_entry now reclaims the slot from the free list —
before, a detached-then-reattached node left its slot in the free
list, so node_count undercounted and the next add_node silently
clobbered the restored node. This was the user's 'undo, redo, undo,
redo and the result changed' bug; regression covered by cycle tests
(move/trim/delete/split/add-track/linked-placement all converge).
- Dropping a video-with-audio footage now places a video clip AND a
linked audio clip at the same range in ONE undoable 'Add Clip' entry
(the links live on NodeCore.links, the canonical links_of storage;
auto-creates the missing track kind).
- The timeline toolbar gains 'Add Video/Audio Track' buttons and the
track-header context menu offers the same two entries above
Delete/Delete All Empty.
- create_sequence now starts every new sequence with the default
2 video + 2 audio track layout (driven directly through the add-track
commands, not through the undo stack). Tests updated for the new
default track counts.
FFmpeg 8 removed the standalone hardware decoders (h264_videotoolbox/
vaapi/nvdec/d3d11va no longer exist in its configure) — hardware decode
now only exists as a hwaccel attached to the software decoder. The new
oakcodec::hwdecode module therefore opens the regular decoder with the
platform's hardware device context attached (VideoToolbox on macOS,
VA-API then NVDEC on Linux, D3D11VA then NVDEC on Windows): FFmpeg
engages the matching hwaccel, decodes into hardware surfaces, and we
transfer them to system memory (NV12/P010) ahead of swscale.
- HardwareDecoding config switch, default ON by mandate; a checkbox in
Preferences > Rendering (EN/ZH); device creation failure skips to the
next candidate and finally to software; a decode-time failure on a
hardware session reopens it as software and retries once.
- hw_decoder_name() observability hook plus a HW_TRANSFERS counter so
tests can prove the hwaccel really engaged (not silently software).
- Verification: demo.mp4 H.264 decodes through VideoToolbox with a
transferred hardware surface, and the pixels match the software
decode within 0.05; switch off forces software.
- build-ffmpeg.sh also enables nvdec when ffnvcodec headers exist.
The C++ viewer Playback Resolution menu, wired end to end: the radio in
the viewer context menu reflects and sets the PlaybackDivider config,
proxy_render_size renders the preview at 480/divider long edge, and
changing the divider invalidates the cached and in-flight preview
frames. This is the escape hatch for machines that cannot keep up with
playback (measured: a debug build of the worker pool reaches only 11
fps vs 152 fps in release on 1080p H.264, which no amount of
scheduling can make realtime).
Two compounding causes behind 'playhead advances but the picture stays
frozen' and 'pause freezes the app':
- The wall-anchored clock teleported the playhead past the pre-render
window during any long stall (the first render after pressing play
costs seconds while the worker pool spins up: measured +104 frames in
one 4.1s block). The window then started behind and, with stale
in-flight frames occupying the workers, never converged.
RealClock::tick now clamps the advance to 2 frames/tick and
re-anchors the dropped time (NLE drop-frames semantics).
- Window frames the playhead had already passed stayed pending/in
flight, burning worker time on frames that could never be displayed.
update_preview_window now cancels them per tick via the new
JobDispatch::cancel_preview_frame, keeping the workers on frames
around the playhead.
Includes a production-shaped regression test (real 1080p media on the
timeline, actual cpu_frame display path) that failed with the exact
production signature (playhead 240 / displayed 0 / 36 stale slots)
before the fix and passes after.
The main-process sample showed the UI thread spending 100% of its time
in TicketArena::wait from the painted frame's synchronous render: every
cache-missed playhead frame sync-rendered inline, and the seek-priority
ticket then stole worker capacity from the pre-render window while the
blocked tick loop could not feed it — a self-reinforcing loop that made
playback unusably choppy.
On a playback miss the viewer now shows the last displayed frame while
the pre-render window warms up/catches up (paused monitors and the very
first frame keep the synchronous path). Adds a gpui test driving real
playback that requires the window to supply playhead frames, and the
real-footage bench_playback example used for the measurements
(152 fps aggregate on 1080p H.264 at 480p preview, decode-bound).
Pressing play froze the app: the 120-frame pre-render window could
hold every shm slot in the pool (e.g. 8 workers x 3 F32 slots = 24 <
120). Once the wall-clock playhead outran the renders, the UI's
synchronous frame wait had no credit to dispatch, and the
slot-releasing cleanup runs on that same blocked UI thread — a hard
deadlock.
The window is now capped to (workers x slots - workers), reserving one
slot per worker so interactive (seek/sync display) and audio tickets
always dispatch. preview_window_capacity is exposed through
JobDispatch; a unit test pins the reserve math.
- preview_footage_media decodes the original from the footage's actual
first stream of the kind instead of hardcoded 0/1, fixing audio-first
and other atypical stream layouts (with a unit test).
- spawn_modal now probes for a nested window update and defers the
build instead of silently dropping the dialog — the phase-7
Preferences/Action Search fix applied centrally to every modal
(export, proxy settings, project manager, progress dialogs).
- The gpui_wgpu atlas no longer double-copies identity-format uploads,
leaving a single CPU staging copy (gpui RenderImage ownership) plus
the GPU upload on the onscreen path; the residual copy and the
IOSurface route to true zero-copy are documented in the M15 design.
- Audio tickets join the process backend: render_audio_batch wire
message, workers mix straight into shm slots (SLOT_FORMAT_AUDIO_F32),
ShmAudio payload with release semantics, crash isolation covers audio
renders; playback audio uses an async 4-chunk prefetch drained on the
UI tick (also fixes the sub-60fps chunk truncation bug); oversized
ranges and dispatcher outages fall back to in-process inline.
- Per-ticket slot formats: force_format is honored (exports request
F32 slots, dropping the BGRA8 round-trip and its 8-bit quantization);
segments grow on demand via worker-idle rebuild with generation
handoff; the scheduler filters over-capacity tickets.
- Adaptive defaults: 128-256MB/worker segment budgets drive slots per
worker, batch size follows workers/slots; bench_process example
measures throughput and adjacent-frame completion deltas
(e.g. 4 workers: 841 fps, 4.6ms mean delta).
- Shortcut override layer over the action registry: <config>/shortcuts
file (id<TAB>keystroke, gpui syntax), loaded before bind_keys at
startup, saved as diff-only (all-default removes the file), conflict
resolution steals the key from its previous owner; rebind_keys
applies changes live (clear_key_bindings + bind_keys + menu rebuild).
- Preferences gains a Keyboard tab: menu-hierarchy action list,
name/path/shortcut filter, click-to-capture key editor (any key
assigns, Backspace unbinds, Esc cancels), Reset Selected/All,
Import/Export.
- Action Search on '/': modal listing 'Menu > Submenu > Action',
live filter, arrows + Enter dispatch through the same path as menu
clicks. Keystroke interception handles capture/search input ahead of
the global keymap; modal opening is deferred to avoid a nested
update_window failure.
- New MulticamPanel: rows/cols angle grid with the current angle
highlighted, click-to-switch, 1-9 switch-and-split and cmd-1-9
switch-only shortcuts (focused-panel routed), deferred switch queue
during playback.
- src/oakui/multicam.rs: clip->connected-sequence resolution, multicam
state detection (selection then playhead fallbacks), per-angle frame
requests rendered through the process backend into an LRU cache.
- Timeline clip context menu Multi-Cam checkable item wired to
oaktimeline::multicam enable/disable with undo.
- Engine trait extended (real + mock); mock drives the real command
path with synthesized angle frames.
- WorkerPool thread pool deleted; RenderManager defaults to the
Processes backend (oak-worker children), Threads kept as a test-only
inline dispatcher; audio tickets stay in-process until S3.
- Onscreen path reads worker shm slots directly: BGRA8 slot format,
RenderedFrame::Shm wrapped into the display buffer (single disclosed
GPU-staging memcpy), scopes analyze BGRA8; the long-lived full-res /
thumbnail paths take the counted slot_to_vec copy and release.
- Playback pre-render window: forward 120 frames (configurable) fed to
the PreviewScheduler at Playback priority, interleaved across
workers, cached in shm slots until the playhead consumes them;
generation-based invalidation cancels and releases on edits.
- oaktask export and oak-cli run on private ProcessDispatchers (fixed
a pump-while-locked self-deadlock in the export loop); facade
get_frame handles ShmFrame payloads.
- Acceptance: preview path main_heap_frame_copies == 0 with spawned
workers, CLI transcode/render verified end to end.
Proxy: preview-path proxy substitution (global UseProxyMedia AND
per-footage enabled AND on-disk ready; export always uses originals),
proxy generate/delete/reveal/enable actions, ProxyDialog with global
and per-footage custom params, Tools menu + context-menu Proxy
submenus, progress in the status bar, OVE serialization of proxy
metadata and source_start_time.
Sync: timeline context-menu Synchronize by Source Time / by Waveform /
by Waveform (Adjust Speed) with ctrl-shift-w, cache-envelope
extraction with validity masks, reference/anchor selection and
single multi-undo application (replace-with-gap, speed adjust,
re-place) mirroring timelinewidget.cpp semantics.
Port the C++ menu/shortcut architecture (origin/main) to the Rust shell:
- src/actions.rs: action registry (123 entries with stable C++ ids,
i18n keys, default key bindings, routing targets) driving both the
menu bar and App::bind_keys; src/shortcuts.rs flat table removed.
- src/panels/commands.rs: PanelCommandHandler trait routing playback,
editing, zoom, markers etc. to the currently focused panel.
- make_menus rebuilt from the registry: full File/Edit/View/Playback/
Sequence/Window/Tools/Help trees aligned with the C++ main menu.
- src/menus/: shared context-menu infrastructure; right-click menus
for timeline (clip/empty/track head/ruler), project explorer,
viewers, node editor, inspector effect stack, with i18n EN/ZH.
Synchronize/Proxy/Multi-Cam entries exist but stay disabled pending
their engine wiring phases.
- graphops: reprobe_unprobed_footage resolves relative filenames against
the project dir and probes footage whose stream metadata is missing
(C++ saves and older Rust files), restoring durations and track kinds
- real: legacy footage is reprobed on open/adopt; a background worker
renders first-frame thumbnails (hash-cached PNGs) and the project
explorer data source attaches them as they complete
Module-internal object references are Rust types now (values, Arc,
Mutex); CHandle remains only at the oakengine C-ABI boundary:
- oakundo: the global stack holds UndoStack/UndoCommand values
directly (stack token is the static's address)
- oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands
share the same allocation through Arc clones (readers in oakengine
stubs and the app's graphops updated to lock)
- oaktask/oakstorage: sessions, write-through bindings and the
database backend pass ProjectArc; the Session drops its manual
release bookkeeping; nodeutil keeps the CHandle<->Arc boundary
conversion (release_project restored for the app)
- oakcodec: handle.rs deleted outright (no facade entry needed it);
texture/block placeholders are unit structs
- oakrender: copier's project handle is an identity u64; alive-count
machinery removed; handle.rs is make_owned/get/get_mut only
- oakplugin: the instance registry is gone (its unregister key never
matched, leaking weak entries); handle.rs is the RefBox boundary type
- oaknode/oakcommon: only dead guard/borrow helpers removed; external
payload handles (texture/processor) documented as the boundary
Flake hunts landed along the way: the audio recording test serializes
on the shared manager lock with a normalized state; the autocacher
cancel test uses a slow producer so cancellation is deterministic.
- track headers: name + visible/mute/lock toggles, undoable through
graphops, honored by the montage (muted tracks are skipped) and by
the edit guards (locked tracks reject trims/moves/deletes)
- project explorer gains a panel title row; the tree/icons toggle is
now small icon buttons with tooltips
- new effect library dock panel (every addable effect; double-click
appends to the selected clip's chain)
- screenshots refreshed (zh/en, window/manager/preferences)
- 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
A fresh sequence has no tracks and a drop rejected with 'display track
does not exist'; wrong-kind drops also rejected. Dropping footage now
creates a matching track first (the Premiere convention), so the
drag-to-timeline path works on empty timelines too.
- full-res source jobs carried only the footage node box: dropping the
project mid-flight left the node dangling (crash in the worker's free
path) and the node was also freed twice (at renderer creation AND at
release). The request now carries an addref'd project copy and the
node is freed exactly once; regression test drops the project before
the worker runs
- the source clock ticked at the SEQUENCE length, so playing footage
with an empty sequence froze the source playhead at 0; the source
clock now loops at the selected footage's probed duration
- oak-cli/oak-worker: DeepSeek's refactor batch (clap migration, engine
FFI consumers); the stale exporter-family test flipped to the real
contract (mp4 is written)
- engine: render_audio smoke test on an empty sequence
- viewers show the 480px proxy immediately and a background thread
fills the sequence-resolution frame (per-monitor in-flight job,
generation-based staleness, playback skips full-res)
- preferences dialog complete: cache dir (now consumed by
default_disk_cache_path), proxy policy/divider, snapshot interval
(write-through era autosave), default transition length, audio
in/out devices (new facade device-enumeration exports; audio init
from config — playback was never creating the audio instance),
language/theme/renderer backend, all persisted via config
- shortcut map (src/shortcuts.rs): space/J/K/L, I/O, S split, A/^A,
⌘Z/⌘⇧Z, ⌘N/⌘O/⌘S/⌘E/⌘Q, frame step, Home, track zoom; dispatch
shares the menu action path and stays silent over modals
- screenshots: preferences dialog zh/en captured and reviewed
- facade: oakengine_sequence_set_workarea_undoable (enable+range as one
undo record); marker add/remove/list already existed and are now
covered by it_timeline (10 new tests: markers, workarea, cross-track)
- fixes: stubs workarea_get honored NULL out-params (is_enabled always
failed); export tasks honor custom ranges (export_params_pod dropped
them; oaktask EncodingParams carries has_custom_range)
- app: markers on the ruler (gpui diamond markers), menu
sequence-add/remove marker, set/clear work area (selected-clip bounds
or playhead+1), ruler drag previews live and commits one undoable
record (C++ ruler semantics); export uses the work area when enabled
- cross-track clip moves were already wired; now covered end to end
- ProjectDataSource<RealEngine> reads the real bin tree (roots/
children via the facade folder/footage enumeration)
- AppEngine::import_footage implemented (facade project_import_footage,
last_error surfaced); double-click opens in the source viewer
- dragging files onto the browser imports them (also fixes a real gpui
bug: ExternalPaths arrives as the bare type, not Arc-wrapped, so
drops never fired)
- tests: real-engine import->browser listing, widget drop routing,
facade folder enumeration failure matrix
D3: oakdb+pg:// fully wired (shared sea-orm entities, BIGSERIAL DDL,
connect-probe instead of pool retry on dead servers); Storage/Backend=pg
+ Storage/PgUrl config; 13 OAK_TEST_PG_URL-gated PG tests (verified
against a Docker postgres:16), always-on clean-error tests otherwise.
D4: DaVinci-style project manager — list with derived stats, create/
rename/duplicate/delete (confirm)/import/export (native dialogs,
ove/otio/fcpxml), shown at startup and from the file menu; facade
oakengine_library_* exports (list/create/delete/rename/duplicate/
import/export + project_load_library that binds write-through);
save/save-as menu becomes 'export project file', open splits into
from-library/from-file; status bar shows library write state; storage
activates on app start and flushes on exit; spawn_modal reentrancy
fixed (window-callback path) with a doc note.
Also: the P1 audio test's environment probe was lost in the ffi purge;
restored on cpal (the output device is cpal now).
Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are
gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/
oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain
Rust, CHandle marshalling shrinks to the oakengine boundary, and tests
call the Rust APIs directly (pure C-ABI wrapper tests removed where
the domain layer already covers the behavior).
exporter.h family implemented: oakengine_export_render (CLI contract),
oakengine_export_render_with_params (was a stub), last_error and
progress callback; synchronous path reuses task_create_export +
start_sync. Fixes on the way: oaktask video ticket self-deadlock,
audio params dropped on the export path, codec encoder AAC slicing and
H.264 time base. Real-mp4 tests cover both entry points, progress and
the illegal-argument matrix.
Also: oakstorage session maps null project handles to None (version-
info path), configstore test double literal 3.14 -> 3.15 (clippy PI
lint), oakaudio output callback scratch buffer + env-aware P1 test,
cli media round-trip test uses a generated 16-frame clip (no more
minute-long debug runs).
facade (API only extended): oakengine_sequence_as_node /
_sequence_node_count / _sequence_node_at / _sequence_remove_node;
remove validates ownership by project UUID (arena slot/generation
collide across projects). it_node covers enumeration/edits and the
NULL/illegal matrix.
app: NodeGraphDataSource enumerates the current sequence's graph —
footage / effect / clip / output cards at their context positions
(deterministic role-grid fallback), real edges plus synthesized
clip->sequence tex_in edges; connect/disconnect/remove/drag-release
all go through undoable facade commands, drag previews stay local.
- screenshot example inits i18n and captures both zh-CN and en-US
(docs/screenshot-window{,-en}.png)
- dock tabs size to content (no more 64px truncation); viewer/panel
titles follow the design (素材查看器·name etc.)
- mock project carries V1/V2 video + A1/A2 audio clips rendered as
rounded green bars; timeline clip geometry/rounded corners match the
design; status bar visible with full content; audio meter strip
docked at the program viewer's right edge; project explorer rows
have icons
- tests/waveform_e2e.rs: waveform cache extracts real peaks and hits
cache on re-query (P4 acceptance)
engine:
- oakrender eval footage hook decodes via oakcodec (JobSpec::Footage
carries filename/stream); ticket/ffi/manager wiring, real-media
decode test with programmatically generated MPEG-2
- oakrender bridge/codec.rs + node.rs: direct oakcodec/oaknode calls;
the crate's dlsym module is gone (project_deep_copy/sync_copy remain
documented always-fail stubs — never implemented in oaknode)
- oakaudio waveform/decoder path adjustments for the decode hook
app (gpui + gpui_widgets):
- menu bar scrubbing: hovering another top-level title while a menu is
open switches to it; popup width is content-aware (CJK-aware) instead
of fixed 160px
- density pass: window rem 16 -> 14px, menu rows 26 -> 22px, dock tabs
32 -> 26px, viewer transport tightened
- open/import/save-as use the native platform file dialogs
(prompt_for_paths / prompt_for_new_path; multi-select import);
MockEngine records imported footage for tests
- project explorer Tree/Icons toggle is localized (explorer.tree /
explorer.icons widget keys)
source monitor:
- new facade exports oakengine_renderer_create_for_node (render any
node, not just sequences) and oakengine_project_footage_at (fetch a
footage node by index); it_render covers the e2e render plus the
NULL/illegal matrix
- RealEngine renders the selected footage's real frames to the source
viewer (per-node renderer slot, frame cache invalidated on selection
change, synthetic fallback kept)
- known gap (documented): pixels stay transparent black until
oakrender's footage decode hook lands — the render surface itself is
real end to end
audio meter:
- oakaudio manager can now report per-channel linear peaks of the
buffered output (PreviewAudioDevice::peek_tail + levelmeter analysis
of the newest 8192 frames, packed/planar F32)
- new facade export oakengine_audio_output_levels (negative codes pass
through); RealEngine's AudioMeterDataSource reads it instead of
returning hardcoded silence
- tests: module peak readback + facade validation/readback matrix
- oakengine_sequence_move_clip implemented for real (oaktimeline
TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
trim bugs the stub was hiding); same-track via the frozen C ABI,
cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
effect_input to it, so timeline clips can host effect chains; facade
test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
dav1d Cellar path, breaking links); reads a git-ignored workspace
.env for IDEs that cannot inject env vars (RustRover); links the C++
stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
it_node uses the direct instance's value type where it calls the
module FFI (the --workspace dev-dependency feature split builds
oaknode twice)
facade (oakengine/oaknode, API only extended):
- oaknode_node_get_effect_input / oaknode_node_get_flags module exports
- oakengine_clip_as_node, oakengine_node_effect_count/at/insert/remove/
move/set_enabled, oakengine_node_identity/is_enabled/get_type_id,
oakengine_node_factory_id_at — chain edits wrap disconnect/reconnect
into single undo groups; insert/move/remove validated against the
factory and the host's effect input
- it_node covers enumeration/insert/remove/move/toggle/undo-redo and
the NULL/illegal-input matrix
app:
- RealEngine binds the selected clip's effect chain to the inspector's
effect stack (cards, enable, reorder, remove, expansion state), all
edits undoable through the facade
- AppEngine gains set_selected_clips / addable_effects / add_effect
(default no-ops keep the mock untouched); the timeline selection
drives the stack target; the inspector gets a small add-effect menu
- known module gap (documented): ClipBlockBehavior sets no effect_input
yet, so timeline clips cannot host effects until oaknode grows tex_in
- program viewer gains Picture/Scopes tabs; the scopes page hosts the
gpui_widgets histogram, waveform and vectorscope side by side
- RealEngine analyzes the same F32 RGBA samples it renders (BT.709 luma,
normalized Cb/Cr); the mock engine analyzes its synthetic frame
through the same path; results ride the per-frame cache
- AppEngine::scope_data(monitor) exposes ScopeData to panels
- unit tests for the analysis math + a gpui test rendering the scopes
tab; zh/en i18n keys added
- RealEngine caches a per-sequence renderer and serves cpu_frame via
oakengine_renderer_render_frame (F32 RGBA, 480px-long-edge proxy)
instead of the synthetic test pattern; edits/undo invalidate the
per-frame cache
- frames.rs: F32 RGBA -> BGRA8 downconvert extracted as
f32_rgba_to_bgra_image (shared with the synthetic fallback)
- ffi.rs: renderer/frame C ABI declarations + OakEngineFrame /
OakEngineRenderer handle mirrors, oakrender_manager_init/available
- new real_render_frame_e2e test renders through the dylib; mock engine
and headless tests unaffected
- known gaps (documented in code): source monitor still synthetic
(facade renders sequences only), render is synchronous on the UI
thread, renderer not rebuilt on sequence geometry change
- App no longer depends on the oakengine rlib: build.rs links the
built liboakengine.dylib (+rpath, -export_dynamic, IOSurface) and
src/oakui/ffi.rs declares the pure-C surface; RealEngine calls only
the frozen oakengine_* C ABI
- host_syms.rs provides the oakcore_*/fb_* host symbols the dylib
imports via dynamic lookup
- Fix Preferences dialog crash (spawn_modal reentrancy) with a
regression test
- Timeline toolbar and viewer transport render C++-era icons (16px
grid, dark/light themes) with localized tooltips
- i18n: complete en-US table, add untranslated-key detection test
- New dialogs module (preferences, export, progress)