Claude/adoring banach bfa376#79
Conversation
Adds two new built-in timeline actions to SpliceKit core:
- `selectForward` — selects all primary-storyline clips whose start
time is at or after the playhead position.
- `selectBackward` — selects all primary-storyline clips whose end
time is at or before the playhead position.
Both actions use `effectiveRangeOfObject:` on the spine collection for
accurate absolute positions and `setSelectedItems:` on the timeline
module for selection — same ObjC patterns used throughout the server.
Clips that span the playhead are excluded from both directions so that
downstream ripple-delete / copy operations are predictable.
Also included:
- `SpliceKitBRAW.mm`: no-op stubs for `SpliceKit_bootstrapBRAWAtLaunchPhase`
and `SpliceKit_handleBRAWAVProbe` under `#if !SPLICEKIT_HAS_BRAW_SDK` so
the dylib links cleanly on machines without the Blackmagic RAW SDK.
- `Makefile`: removed `braw-prototype` from the hard prerequisites of the
`deploy` target; it is still run conditionally when ENABLE_BRAW_PROTOTYPE=1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Native ObjC plugin that injects two buttons directly into Final Cut Pro's
main toolbar using the SpliceKit plugin API:
◀| Select Backward — selects all primary-storyline clips whose end
time is at or before the playhead position.
|▶ Select Forward — selects all primary-storyline clips whose start
time is at or after the playhead position.
Each button invokes the new `selectForward` / `selectBackward` timeline
actions added to SpliceKit core in this same PR.
Implementation notes:
- Swizzles `PEMainWindowModule`'s toolbar delegate method using
`class_replaceMethod` (not `method_setImplementation`) so we only
touch the specific class's own method table, avoiding superclass
side-effects that caused FCP crashes in earlier iterations.
- The `SpliceKitPluginAPI` struct is copied into static storage at
init time. The loader allocates it on the stack; storing the raw
pointer and using it from an async block (after the stack frame is
gone) is undefined behaviour and was the root cause of the crash.
- Swizzle is installed eagerly on the main thread before the
notification/polling loop starts, so the identifier is always
resolvable the moment `insertItemWithItemIdentifier:atIndex:` runs.
- Stale (view-less) items are cleaned up on each installation attempt,
handling the edge-case where a previous run inserted items before
the swizzle was in place.
- Buttons use SF Symbols (`arrow.right.square` / `arrow.left.square`)
at 13 pt Medium weight to match FCP's native toolbar aesthetic.
Build & install:
cd examples/plugins/com.splicekit.select-from-playhead-buttons
make install # builds universal dylib and copies to ~/Library/…
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…gning Replace at Playhead (SpliceKitReplaceAtPlayhead.m): - Re-injects "Replace at Playhead" into FFAnchoredTimelineModule.dropMenu (LKMenu) after "Replace from End", wired to actionDropMenuReplaceAtPlayhead: - Registers LKCommand bound to Option+Shift+R in FCP's Command Editor - Drag-drop path: intercepts operationReplaceItem:withItems:replaceActionType:1 to apply correct source in-point (srcIn = sourcePlayheadTime − (playheadTime − targetStart)) and trim to original target clip duration via setTimeRangeInAsset: - Keyboard path: reads browser cursor from FFOrganizerFilmstripModule.currentSequenceTime (organizer-cumulative coords), walks primary storyline to find target clip geometry, arms _deferredDropTargetItem so FCP sizes the clip correctly, then triggers performEditAction:fromPasteboardWithName: with replace type=1; operationReplaceItem: swizzle substitutes the clippedRange with orgClipStart+desiredSrcIn coords Undo History palette (SpliceKitUndoHistoryPanel.h/.m): - Floating panel showing every action on FCP's undo stack including pre-open history - Click any row to undo/redo to that state; redo entries shown greyed below cursor - Toolbar button (clock icon) + Splices menu item (Ctrl+Option+U) - Tracks up to 100 entries via NSUndoManagerDidCloseUndoGroupNotification with nesting-depth gating so only outermost group closes produce entries Makefile deploy signing fixes: - Strip com.apple.provenance xattrs from PlugIns before signing (was causing codesign to fail with "resource fork not allowed", silently breaking deploys) - Add explicit codesign step for SpliceKitMKVImport.bundle (was missing, causing app-level signing to fail with "code object is not signed at all") Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds "Paste Overwrite" immediately after the native Paste item in FCP's
Edit menu (Cmd+Shift+V). Replaces timeline content at the playhead with
clipboard clips without changing timeline duration.
Algorithm: paste: (inserts clipboard at T, shifts content right) →
blade: at T+2D (isolates D seconds of original content in [T+D, T+2D]) →
_selectRange:{T+D,D} (selects all real clips in that segment) →
clearRange: + deleteSelectionOnly: (ripple-deletes only the selected clips).
No gap clips involved — avoids the gap-selection problem entirely.
Clipboard duration is read directly from FCPXML on the pasteboard
(IXXMLPasteboardType) to avoid a probe paste. Falls back to probe-paste
measurement when FCPXML is unavailable.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The patcher checks CFBundleShortVersionString in the installed SpliceKit.framework to decide whether to prompt for an update. The old Makefile only created Info.plist when it was missing, and used a hardcoded "1.0.0" version. After any manual deploy that recreated the plist, the version mismatch caused the patcher to block FCP launch with a spurious "update available" prompt. Fix: always overwrite Info.plist on every make deploy, writing both CFBundleShortVersionString and CFBundleVersion from the SPLICEKIT_VERSION variable (currently 3.3.8). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lete protection
Locked clips cannot be moved, trimmed, deleted, cut, or have effects dropped on them.
Deleting an unlocked clip when locked clips exist redirects delete: → shiftDelete:
(gap delete) so locked clips never shift position.
Key lesson: operationRemoveEdits:...playhead:error: has type encoding ^{?=qiIq} for
the playhead arg — a pointer to CMTime, not CMTime by value. Using the wrong type
caused EXC_BAD_ACCESS (null ptr deref at FAR=0) on every delete. The swizzle was
removed; delete: interception handles the use case safely.
Also adds: transition-alignment plugin, undo-history-ui plugin, independent-slip
plugin (disabled), Replace at Playhead command palette entry, history.* and clips.*
bridge metadata, SelectFromPlayheadButtons reworked to timeline bar with direct
setSelectedItems: calls, Paste Overwrite ⌥V shortcut.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new File > Close Other Libraries command that closes all open libraries except the currently focused one (_targetLibrary). Grayed out when only one library is open. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a dedicated SpliceKit tab to FCP's built-in Settings window (alongside the existing Debug pane). Subclasses PEAppDebugPreferencesModule and re-populates _masterPreferenceViews after _setupToolbar so both tabs appear in a single rebuild. Moves FFDontCoalesceGaps into the SpliceKit pane and adds inline descriptions to DebugUI checkbox groups. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a persistent tab bar between the viewer and timeline decks showing all open projects as clickable tabs. Uses LKContainerView (flipped coords), matches tabs by pointer→UID→name to avoid restart duplicates, and persists state to timeline_tabs.json. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cts (⌥⇧⌘R)
New command registered as LKCommand "SKReplaceRetainingAttributes" bound to ⌥⇧⌘R,
appearing in the Command Editor under the Editing group alongside the other Replace
variants (Replace at Playhead, Replace From Start, etc.).
Replaces the timeline clip at the playhead with the browser selection while
preserving all effects, color corrections, transforms, and audio attributes
of the original clip:
1. select clip at playhead → copy: on the timeline module directly
(sendAction:to:nil routes to the browser when it has focus, so we
target the timeline module via SpliceKit_getActiveTimelineModule)
2. replaceWithSelectedMediaAtPlayhead: via sendAction (uses a private
named pasteboard — does not disturb copy:'s data)
3. dispatch_async one runloop cycle to let the timeline model settle
4. selectClipAtPlayhead: + pasteAllAttributes: on the timeline module
("Edit > Paste Effects" fires pasteAllAttributes:, not pasteEffects:)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rm/favorites exit New plugin for cycling through transitions at an edit point via a timeline bar button. Features a tournament bracket (👍 Add / 👎 Reject) that narrows a full or custom set down to a winner. Any point during the audition, two exit buttons let the user commit the current transition immediately: - "Confirm Transition and Exit" — keeps the applied transition, stops playback - "Add to Favorites and Exit" — same, plus adds to FCP Favorites and the plugin's Custom Set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Swizzles PETimelineItemLayer.updateAppearance: — when wantsFilmstripLayer returns NO (Change Appearance → no thumbnails), expands _audioContentsLayer to fill the full clip bounds instead of leaving the filmstrip area vacant. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Injects a SpliceKitShortcutsBar into the Auto Layout flex gap between the history-forward button (PEEditorMenuDelayButton) and the snapping/skimming control (LKPaneCapSegmentedControl) in FCP's timeline toolbar bar. - 44 assignable actions across 8 categories (Edit, Markers, Navigation, Color, Rating, Speed, Trim, View) with SF Symbol icons and tooltips - Config persisted to ~/Library/Application Support/SpliceKit/timecode_bar_shortcuts.json - Hot-reload: SpliceKit_setTimecodeBarConfig() swaps buttons live without restart - Preferences pane section lets users toggle buttons via checkboxes grouped by category - Constraint search fix: Auto Layout normalizes H:[A]-(>=N)-[B] so firstItem=B, not A — corrected SCB_findFlexConstraint to match actual storage order - Robust reload: uses shortcutsBar.superview as the sole install-state indicator; retries on both toolbar-not-found AND constraint-not-found paths - Right-anchor fix: SCB_findRightAnchor() walks the trailing/leading == chain leftward from snappingCtrl to find the leftmost existing pinned button (transition audition + clip buttons), preventing our bar from overlapping them Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n playhead-select buttons
FCP Defaults (PreferencesPane.m, Server.m):
- Persistent answers for FCP's "not enough media" transition dialog
(ask / freeze frames / ripple trim), plus default clip height, audio
channel config, and audio pan mode — settable via set_bridge_option_value
or the new Preferences pane section, applied automatically so the
dialog never interrupts the workflow.
WaveformExpand:
- Swizzles FFFilmstripCell.audioHeight instead of
PETimelineItemLayer.updateAppearance:. Root cause: the -1 sentinel
("use appearance default") was being drawn literally as 28px even
though the container expanded to 96px. Now returns the cell's actual
layer height when thumbnails are hidden.
SelectFromPlayheadButtons (example plugin):
- Bumped icon point size 11->19 and switched button height to track
the segmented control's height anchor instead of a fixed 20pt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
30 issues found across 36 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/SpliceKitUndoHistoryPanel.m">
<violation number="1" location="Sources/SpliceKitUndoHistoryPanel.m:271">
P1: Redo branch is not pruned when recording a new action from a non-tip cursor position, causing history/state drift.</violation>
</file>
<file name="examples/plugins/com.splicekit.select-from-playhead-buttons/src/SelectFromPlayheadButtons.m">
<violation number="1" location="examples/plugins/com.splicekit.select-from-playhead-buttons/src/SelectFromPlayheadButtons.m:247">
P2: Timeline-bar discovery uses a brittle fixed superview depth (exactly 4 levels) instead of the flexible-depth + content-matching pattern used in the core codebase.</violation>
</file>
<file name="examples/plugins/com.splicekit.independent-slip/Makefile">
<violation number="1" location="examples/plugins/com.splicekit.independent-slip/Makefile:9">
P2: Build paths are anchored to `$(shell pwd)` instead of the Makefile's directory, making compilation and install behavior dependent on the caller's current working directory.</violation>
</file>
<file name="examples/plugins/com.splicekit.select-from-playhead-buttons/Makefile">
<violation number="1" location="examples/plugins/com.splicekit.select-from-playhead-buttons/Makefile:9">
P2: Makefile path resolution is fragile because it relies on `$(shell pwd)` instead of the Makefile's own directory. If invoked via `make -f` from another directory, all relative paths (`src/...`, `build/...`, `clean`) operate on the wrong location, which can cause build failures and potentially delete unintended directories.</violation>
</file>
<file name="examples/plugins/com.splicekit.undo-history-ui/Makefile">
<violation number="1" location="examples/plugins/com.splicekit.undo-history-ui/Makefile:9">
P2: PLUGIN_ROOT uses $(shell pwd) which binds to the caller's current working directory rather than the Makefile's own location, making builds fragile when invoked from outside the plugin directory</violation>
<violation number="2" location="examples/plugins/com.splicekit.undo-history-ui/Makefile:19">
P2: Unquoted include path `-I$(REPO_ROOT)/Sources` will break compilation when the repository absolute path contains spaces.</violation>
</file>
<file name="Sources/SpliceKitReplaceAtPlayhead.m">
<violation number="1" location="Sources/SpliceKitReplaceAtPlayhead.m:46">
P1: `SK_CMTimeSubtract` and `SK_CMTimeAdd` divide by `b.timescale` in the mixed-timescale path without checking for zero, causing SIGFPE crashes when an invalid CMTime (timescale==0) is passed. Multiple concrete crash paths exist, including post-correction logging and the main drag-drop execution path.</violation>
<violation number="2" location="Sources/SpliceKitReplaceAtPlayhead.m:55">
P3: `SK_CMTimeNegate` is defined but never used in this file — remove this dead code.</violation>
</file>
<file name="examples/plugins/com.splicekit.transition-alignment/plugin.json">
<violation number="1" location="examples/plugins/com.splicekit.transition-alignment/plugin.json:16">
P2: Method params use freeform strings instead of structured schema objects, which deviates from the established plugin manifest format used elsewhere in this repository.</violation>
</file>
<file name="Sources/SpliceKitPreferencesPane.m">
<violation number="1" location="Sources/SpliceKitPreferencesPane.m:590">
P1: Missing nil guard before `addObject:` when reading persisted config keys</violation>
<violation number="2" location="Sources/SpliceKitPreferencesPane.m:811">
P2: Potential out-of-bounds access: loop iterates by `titles.count` but indexes `modules[i]` without verifying `i < modules.count`</violation>
</file>
<file name="Makefile">
<violation number="1" location="Makefile:383">
P1: Removing `braw-prototype` from deploy prerequisites masks BRAW build/copy failures</violation>
</file>
<file name="examples/plugins/com.splicekit.transition-alignment/src/TransitionAlignment.m">
<violation number="1" location="examples/plugins/com.splicekit.transition-alignment/src/TransitionAlignment.m:186">
P3: Dead local variable `transition` is assigned but never used. It appears to be leftover from an intended unwrapping step that was never implemented.</violation>
<violation number="2" location="examples/plugins/com.splicekit.transition-alignment/src/TransitionAlignment.m:284">
P1: `scope` parameter is not validated; invalid values silently execute as `all` and may modify unintended transitions.</violation>
<violation number="3" location="examples/plugins/com.splicekit.transition-alignment/src/TransitionAlignment.m:305">
P1: Deadlock when TA_handleSetAlignment is called from the main thread: dispatch_async to main queue followed by dispatch_semaphore_wait(DISPATCH_TIME_FOREVER) blocks the main thread before the queued block can run.</violation>
</file>
<file name="examples/plugins/com.splicekit.transition-auditions/src/TransitionAuditions.m">
<violation number="1" location="examples/plugins/com.splicekit.transition-auditions/src/TransitionAuditions.m:240">
P1: Missing 10-second proximity guard in nearest-transition selection: documentation states the nearest transition should only be used if within 10 seconds of the playhead, but the code returns the result unconditionally.</violation>
<violation number="2" location="examples/plugins/com.splicekit.transition-auditions/src/TransitionAuditions.m:338">
P2: Computed transition handle is ignored; live object re-identified by fuzzy 1-second midpoint match, risking deletion of the wrong transition</violation>
<violation number="3" location="examples/plugins/com.splicekit.transition-auditions/src/TransitionAuditions.m:1115">
P1: When all transitions are rejected (kept == 0), the original pre-existing transition deleted at audition start is not restored, causing data loss. The `kept == 0` path in `endRound` should restore the original transition via undo when `hadOriginalTransition` is YES, matching the `cancelAudition:` behavior.</violation>
</file>
<file name="Sources/SpliceKit.m">
<violation number="1" location="Sources/SpliceKit.m:549">
P1: 'Close Other Libraries' can close the currently active library when active-library resolution fails (activeLib nil path).</violation>
</file>
<file name="Sources/SpliceKitTimelineTabs.m">
<violation number="1" location="Sources/SpliceKitTimelineTabs.m:452">
P1: Tab identity fallback by display name can merge distinct sequences sharing the same name, causing data loss in tab state.</violation>
<violation number="2" location="Sources/SpliceKitTimelineTabs.m:551">
P2: `_loadTabState` lacks runtime type validation for persisted JSON array elements and values, creating a crash vector from malformed disk files.</violation>
<violation number="3" location="Sources/SpliceKitTimelineTabs.m:601">
P1: Layout reduction in _applyLowerDeckReduction is non-idempotent: it computes newDeckFrame from the current (potentially already-reduced) frame and the 'already applied' guard (comparing current height against current height minus 28px) always evaluates to false, so repeated frame-change notifications cause cumulative 28px shrinkage.</violation>
</file>
<file name="Sources/SpliceKitTimecodeBarShortcuts.m">
<violation number="1" location="Sources/SpliceKitTimecodeBarShortcuts.m:182">
P1: Config entries are not schema-validated before dictionary subscripting, creating a crash path for malformed JSON/RPC input.</violation>
<violation number="2" location="Sources/SpliceKitTimecodeBarShortcuts.m:321">
P2: SCB_findFlexConstraint under-constrains its Auto Layout search: it matches only by relation (>=) and view class names, ignoring firstAttribute/secondAttribute. This can accidentally select and deactivate an unrelated >= constraint between the same views.</violation>
<violation number="3" location="Sources/SpliceKitTimecodeBarShortcuts.m:416">
P2: Empty configuration still triggers constraint surgery, inserting an empty bar and changing toolbar spacing from a single >=5 gap to two >=6 gaps around a zero-width view.</violation>
</file>
<file name="Sources/SpliceKitClipLock.m">
<violation number="1" location="Sources/SpliceKitClipLock.m:895">
P1: Persisted lock enforcement relies on an in-memory cache (`sSessionLockedClips`) that is only populated asynchronously, creating a 0.3–1.0 second window after startup/sequence load where previously locked clips are unprotected. Lock enforcement swizzles use `CL_isLockedClip()`, which is cache-only, but the cache is populated via `dispatch_after` in `_startListeningToSequence:` (0.3s delay) and a global startup scan (1.0s delay). During this window, delete/trim/move/volume operations on previously locked clips will incorrectly succeed.</violation>
</file>
<file name="examples/plugins/com.splicekit.undo-history-ui/src/UndoHistoryUI.m">
<violation number="1" location="examples/plugins/com.splicekit.undo-history-ui/src/UndoHistoryUI.m:346">
P2: `getHistory` unconditionally performs `dispatch_sync(dispatch_get_main_queue(), …)`. If the RPC is invoked from the main thread, this will deadlock. The same class's `jumpToIndex` correctly uses `SpliceKit_executeOnMainThread`, which checks `[NSThread isMainThread]` before syncing and is the codebase-wide standard for synchronous RPC helpers.</violation>
<violation number="2" location="examples/plugins/com.splicekit.undo-history-ui/src/UndoHistoryUI.m:408">
P1: `_suppressRecord` can get stuck enabled if undo/redo throws, permanently suppressing history recording.</violation>
</file>
<file name="Sources/SpliceKitServer.m">
<violation number="1" location="Sources/SpliceKitServer.m:9843">
P2: `defaultClipHeight` option handler accepts arbitrary positive integers despite advertising constrained values (0/35/80/153), risking invalid persisted state in FCP's defaults.</violation>
<violation number="2" location="Sources/SpliceKitServer.m:13444">
P2: Clearing default clip height does not clear the mirrored FCP preference key (`FFOrganizedTimelineClipHeight`), leaving the override to persist for new projects/sessions.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| _cursor = MAX(-1, _cursor - 1); | ||
| } | ||
|
|
||
| SpliceKitUndoEntry *entry = [[SpliceKitUndoEntry alloc] init]; |
There was a problem hiding this comment.
P1: Redo branch is not pruned when recording a new action from a non-tip cursor position, causing history/state drift.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/SpliceKitUndoHistoryPanel.m, line 271:
<comment>Redo branch is not pruned when recording a new action from a non-tip cursor position, causing history/state drift.</comment>
<file context>
@@ -0,0 +1,465 @@
+ _cursor = MAX(-1, _cursor - 1);
+ }
+
+ SpliceKitUndoEntry *entry = [[SpliceKitUndoEntry alloc] init];
+ entry.actionName = name;
+ entry.timestamp = [NSDate date];
</file context>
| return (SK_CMTime){a.value - b.value, a.timescale, a.flags, 0}; | ||
| // Normalize b to a's timescale to avoid int32 overflow when timescales differ | ||
| // (e.g. 48000 × 90000 = 4.3B > INT32_MAX). Rounding error < 1/a.timescale seconds. | ||
| int64_t b_in_a = ((int64_t)b.value * a.timescale + b.timescale / 2) / b.timescale; |
There was a problem hiding this comment.
P1: SK_CMTimeSubtract and SK_CMTimeAdd divide by b.timescale in the mixed-timescale path without checking for zero, causing SIGFPE crashes when an invalid CMTime (timescale==0) is passed. Multiple concrete crash paths exist, including post-correction logging and the main drag-drop execution path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/SpliceKitReplaceAtPlayhead.m, line 46:
<comment>`SK_CMTimeSubtract` and `SK_CMTimeAdd` divide by `b.timescale` in the mixed-timescale path without checking for zero, causing SIGFPE crashes when an invalid CMTime (timescale==0) is passed. Multiple concrete crash paths exist, including post-correction logging and the main drag-drop execution path.</comment>
<file context>
@@ -0,0 +1,1089 @@
+ return (SK_CMTime){a.value - b.value, a.timescale, a.flags, 0};
+ // Normalize b to a's timescale to avoid int32 overflow when timescales differ
+ // (e.g. 48000 × 90000 = 4.3B > INT32_MAX). Rounding error < 1/a.timescale seconds.
+ int64_t b_in_a = ((int64_t)b.value * a.timescale + b.timescale / 2) / b.timescale;
+ return (SK_CMTime){a.value - b_in_a, a.timescale, a.flags, 0};
+}
</file context>
|
|
||
| // Build a set of currently-enabled action IDs for O(1) lookup | ||
| NSMutableSet *enabledIDs = [NSMutableSet set]; | ||
| for (NSDictionary *item in config) [enabledIDs addObject:item[@"id"]]; |
There was a problem hiding this comment.
P1: Missing nil guard before addObject: when reading persisted config keys
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/SpliceKitPreferencesPane.m, line 590:
<comment>Missing nil guard before `addObject:` when reading persisted config keys</comment>
<file context>
@@ -0,0 +1,964 @@
+
+ // Build a set of currently-enabled action IDs for O(1) lookup
+ NSMutableSet *enabledIDs = [NSMutableSet set];
+ for (NSDictionary *item in config) [enabledIDs addObject:item[@"id"]];
+
+ // Group by category, emit a compact label + row of checkboxes per group
</file context>
| @echo "Staged: $(BRAW_RAWPROC_BUNDLE)" | ||
|
|
||
| deploy: $(OUTPUT) $(SILENCE_DETECTOR) $(STRUCTURE_ANALYZER) $(MIXER_APP) braw-prototype vp9-prototype mkv-prototype | ||
| deploy: $(OUTPUT) $(SILENCE_DETECTOR) $(STRUCTURE_ANALYZER) $(MIXER_APP) vp9-prototype mkv-prototype |
There was a problem hiding this comment.
P1: Removing braw-prototype from deploy prerequisites masks BRAW build/copy failures
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Makefile, line 383:
<comment>Removing `braw-prototype` from deploy prerequisites masks BRAW build/copy failures</comment>
<file context>
@@ -380,7 +380,7 @@ braw-prototype: $(BRAW_IMPORT_EXEC) $(BRAW_DECODER_EXEC) $(BRAW_CLI_BIN)
@echo "Staged: $(BRAW_RAWPROC_BUNDLE)"
-deploy: $(OUTPUT) $(SILENCE_DETECTOR) $(STRUCTURE_ANALYZER) $(MIXER_APP) braw-prototype vp9-prototype mkv-prototype
+deploy: $(OUTPUT) $(SILENCE_DETECTOR) $(STRUCTURE_ANALYZER) $(MIXER_APP) vp9-prototype mkv-prototype
@echo "=== Deploying SpliceKit to modded FCP ==="
@rm -rf "$(FW_DIR)"
</file context>
| if (!errorMsg) changed = transitions.count; | ||
| dispatch_semaphore_signal(sem); | ||
| }); | ||
| dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); |
There was a problem hiding this comment.
P1: Deadlock when TA_handleSetAlignment is called from the main thread: dispatch_async to main queue followed by dispatch_semaphore_wait(DISPATCH_TIME_FOREVER) blocks the main thread before the queued block can run.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/plugins/com.splicekit.transition-alignment/src/TransitionAlignment.m, line 305:
<comment>Deadlock when TA_handleSetAlignment is called from the main thread: dispatch_async to main queue followed by dispatch_semaphore_wait(DISPATCH_TIME_FOREVER) blocks the main thread before the queued block can run.</comment>
<file context>
@@ -0,0 +1,341 @@
+ if (!errorMsg) changed = transitions.count;
+ dispatch_semaphore_signal(sem);
+ });
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
+
+ if (errorMsg) return @{@"error": errorMsg};
</file context>
|
|
||
| - (NSDictionary *)getHistory { | ||
| __block NSDictionary *result = nil; | ||
| dispatch_sync(dispatch_get_main_queue(), ^{ |
There was a problem hiding this comment.
P2: getHistory unconditionally performs dispatch_sync(dispatch_get_main_queue(), …). If the RPC is invoked from the main thread, this will deadlock. The same class's jumpToIndex correctly uses SpliceKit_executeOnMainThread, which checks [NSThread isMainThread] before syncing and is the codebase-wide standard for synchronous RPC helpers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/plugins/com.splicekit.undo-history-ui/src/UndoHistoryUI.m, line 346:
<comment>`getHistory` unconditionally performs `dispatch_sync(dispatch_get_main_queue(), …)`. If the RPC is invoked from the main thread, this will deadlock. The same class's `jumpToIndex` correctly uses `SpliceKit_executeOnMainThread`, which checks `[NSThread isMainThread]` before syncing and is the codebase-wide standard for synchronous RPC helpers.</comment>
<file context>
@@ -0,0 +1,676 @@
+
+- (NSDictionary *)getHistory {
+ __block NSDictionary *result = nil;
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ NSMutableArray *arr = [NSMutableArray arrayWithCapacity:self->_entries.count];
+ NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
</file context>
| dispatch_sync(dispatch_get_main_queue(), ^{ | |
| + SpliceKit_executeOnMainThread(^{ |
| } | ||
| SpliceKit_setTransitionWarningDefault(value); | ||
| return @{@"status": @"ok", @"transitionWarningDefault": SpliceKit_getTransitionWarningDefault()}; | ||
| } else if ([option isEqualToString:@"defaultClipHeight"]) { |
There was a problem hiding this comment.
P2: defaultClipHeight option handler accepts arbitrary positive integers despite advertising constrained values (0/35/80/153), risking invalid persisted state in FCP's defaults.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/SpliceKitServer.m, line 9843:
<comment>`defaultClipHeight` option handler accepts arbitrary positive integers despite advertising constrained values (0/35/80/153), risking invalid persisted state in FCP's defaults.</comment>
<file context>
@@ -9447,6 +9829,44 @@ static id SpliceKit_backgroundRenderJSONValue(id value) {
+ }
+ SpliceKit_setTransitionWarningDefault(value);
+ return @{@"status": @"ok", @"transitionWarningDefault": SpliceKit_getTransitionWarningDefault()};
+ } else if ([option isEqualToString:@"defaultClipHeight"]) {
+ NSNumber *value = params[@"value"];
+ if (!value) return @{@"error": @"'value' required (0=off, 35=small, 80=medium, 153=large)"};
</file context>
| } | ||
|
|
||
| void SpliceKit_setDefaultClipHeight(NSInteger pixelHeight) { | ||
| if (pixelHeight <= 0) { |
There was a problem hiding this comment.
P2: Clearing default clip height does not clear the mirrored FCP preference key (FFOrganizedTimelineClipHeight), leaving the override to persist for new projects/sessions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/SpliceKitServer.m, line 13444:
<comment>Clearing default clip height does not clear the mirrored FCP preference key (`FFOrganizedTimelineClipHeight`), leaving the override to persist for new projects/sessions.</comment>
<file context>
@@ -12818,6 +13402,118 @@ void SpliceKit_installDefaultSpatialConformType(void) {
+}
+
+void SpliceKit_setDefaultClipHeight(NSInteger pixelHeight) {
+ if (pixelHeight <= 0) {
+ [[NSUserDefaults standardUserDefaults]
+ removeObjectForKey:@"SpliceKitDefaultClipHeight"];
</file context>
| int64_t b_in_a = ((int64_t)b.value * a.timescale + b.timescale / 2) / b.timescale; | ||
| return (SK_CMTime){a.value + b_in_a, a.timescale, a.flags, 0}; | ||
| } | ||
| static inline SK_CMTime SK_CMTimeNegate(SK_CMTime t) { t.value = -t.value; return t; } |
There was a problem hiding this comment.
P3: SK_CMTimeNegate is defined but never used in this file — remove this dead code.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/SpliceKitReplaceAtPlayhead.m, line 55:
<comment>`SK_CMTimeNegate` is defined but never used in this file — remove this dead code.</comment>
<file context>
@@ -0,0 +1,1089 @@
+ int64_t b_in_a = ((int64_t)b.value * a.timescale + b.timescale / 2) / b.timescale;
+ return (SK_CMTime){a.value + b_in_a, a.timescale, a.flags, 0};
+}
+static inline SK_CMTime SK_CMTimeNegate(SK_CMTime t) { t.value = -t.value; return t; }
+static inline double SK_CMTimeGetSeconds(SK_CMTime t) {
+ return t.timescale ? (double)t.value / t.timescale : 0.0;
</file context>
| if (!overlapVal) return menu; | ||
|
|
||
| // The item IS or WRAPS a transition. Use it directly as the target. | ||
| id transition = item; |
There was a problem hiding this comment.
P3: Dead local variable transition is assigned but never used. It appears to be leftover from an intended unwrapping step that was never implemented.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/plugins/com.splicekit.transition-alignment/src/TransitionAlignment.m, line 186:
<comment>Dead local variable `transition` is assigned but never used. It appears to be leftover from an intended unwrapping step that was never implemented.</comment>
<file context>
@@ -0,0 +1,341 @@
+ if (!overlapVal) return menu;
+
+ // The item IS or WRAPS a transition. Use it directly as the target.
+ id transition = item;
+
+ // Create the submenu items. The representedObject carries the transition
</file context>
New Features
Timeline Tabs — A persistent tab bar above the timeline showing all open projects as clickable tabs. Tabs persist across restarts and are stored in timeline_tabs.json.
ClipLock — Per-clip locking via ⌥L shortcut. Locked clips show a 🔒 badge and are protected from accidental deletion. Also exposed via RPC (clip.lock, clip.unlock).
SpliceKit Preferences Pane — A native preferences module in FCP's Settings window (alongside Playback, Import, etc.). Houses toggles for all SpliceKit options and the new FCP Defaults section.
FCP Defaults — Saves your preferred answer for common FCP dialogs: the "not enough media" transition warning (Ask / Use Freeze Frames / Ripple Trim), default clip height, audio channel config, and audio pan mode. Configurable via Preferences or set_bridge_option_value.
Transition Auditions — A toolbar button that opens a tournament-style audition panel for cycling through transitions at an edit point. Supports 👍/👎 bracket elimination with "Confirm and Exit" / "Add to Favorites and Exit" options.
WaveformExpand — When video thumbnails are hidden via Change Appearance, audio waveforms expand to fill the full clip height. Fixed to swizzle FFFilmstripCell.audioHeight (root cause) rather than the layer layout pass.
Replace and Retain Attributes (⌥⇧⌘R) — Replaces the clip at the playhead with the browser selection while preserving all effects, color corrections, transforms, and audio attributes of the original.
Paste Overwrite (⌥V) — Pastes clipboard content as an overwrite edit at the playhead (rather than FCP's default connected paste).
Close Other Libraries (⇧W) — Closes all open libraries except the active one, added to the File menu.
Customizable Shortcut Buttons — A row of user-configurable buttons injected into the timeline toolbar's flex gap. 44 assignable actions across 8 categories with SF Symbol icons. Config hot-reloads without restart.
Example Plugin
com.splicekit.select-from-playhead-buttons — Adds Select Forward / Select Backward buttons to the timeline bar, demonstrating the plugin framework for toolbar UI injection.
Summary by cubic
Adds major timeline UX and editing upgrades: persistent Timeline Tabs, per‑clip locking, a SpliceKit Preferences pane with FCP defaults, new replace/paste modes, and an Undo History palette. Also ships a customizable timeline toolbar, transition auditioning, and build/deploy fixes.
New Features
com.splicekit.select-from-playhead-buttons.clips.lock/unlock/toggleLock/isLocked/listLocked/unlockAll).history.show/hide/get/jumpToIndex/clear).com.splicekit.transition-auditions,com.splicekit.transition-alignment,com.splicekit.independent-slip,com.splicekit.undo-history-ui.Dependencies
braw-prototypeas a harddeployprereq.SpliceKit_bootstrapBRAWAtLaunchPhaseandSpliceKit_handleBRAWAVProbeso builds succeed without the Blackmagic RAW SDK.Written for commit f39374b. Summary will update on new commits.