import { createRequire } from "node:module"; import { describe, expect, it, vi } from "vitest"; const require = createRequire(import.meta.url); const Commands = require("../../uxp-plugin/commands.cjs"); const Events = require("../../uxp-plugin/events.cjs"); const Protocol = require("../../uxp-plugin/protocol.cjs"); type MutableItem = { id: string; name: string; isFolder?: boolean; isClip?: boolean; type?: number; parent?: MutableItem | null; children?: MutableItem[]; color?: number; getId: ReturnType; getItems?: ReturnType; getParentBin: ReturnType; getColorLabelIndex: ReturnType; isSequence?: ReturnType; isMergedClip?: ReturnType; isMulticamClip?: ReturnType; isOffline?: ReturnType; getContentType?: ReturnType; getSequence?: ReturnType; createSetNameAction: ReturnType; createSetColorLabelAction: ReturnType; createBinAction?: ReturnType; createSmartBinAction?: ReturnType; createMoveItemAction?: ReturnType; createRemoveItemAction?: ReturnType; createSubClipAction?: ReturnType; }; function advancedHost() { let nextItem = 1; const makeItem = (id: string, name: string, options: Partial = {}): MutableItem => { const item = { id, name, color: 0, type: options.isFolder ? 2 : 1, parent: null, children: options.isFolder ? [] : undefined, ...options, } as MutableItem; item.getId = vi.fn(async () => item.id); item.getItems = options.isFolder ? vi.fn(async () => item.children || []) : undefined; item.getParentBin = vi.fn(async () => item.parent); item.getColorLabelIndex = vi.fn(async () => item.color); item.createSetNameAction = vi.fn((name: string) => ({ apply: () => { item.name = name; } })); item.createSetColorLabelAction = vi.fn((color: number) => ({ apply: () => { item.color = color; } })); if (options.isFolder) { item.createBinAction = vi.fn((name: string) => ({ apply: () => { const created = makeItem(`created-bin-${nextItem++}`, name, { isFolder: true, parent: item }); item.children?.push(created); } })); item.createSmartBinAction = vi.fn((name: string) => ({ apply: () => { const created = makeItem(`smart-bin-${nextItem++}`, name, { isFolder: true, parent: item }); item.children?.push(created); } })); item.createMoveItemAction = vi.fn((child: MutableItem, destination: MutableItem) => ({ apply: () => { if (child.parent?.children) child.parent.children = child.parent.children.filter((value) => value !== child); destination.children?.push(child); child.parent = destination; } })); item.createRemoveItemAction = vi.fn((child: MutableItem) => ({ apply: () => { item.children = item.children?.filter((value) => value !== child); } })); } if (options.isClip) { item.createSubClipAction = vi.fn((name: string) => ({ apply: () => { const created = makeItem(`subclip-${nextItem++}`, name, { isClip: true, parent: item.parent }); item.parent?.children?.push(created); } })); } return item; }; const root = makeItem("root", "Root", { isFolder: true }); const bin = makeItem("bin-1", "Rushes", { isFolder: true, parent: root }); const clip = makeItem("clip-1", "Interview.mov", { isClip: true, parent: bin }); clip.isSequence = vi.fn(async () => false); clip.isMergedClip = vi.fn(async () => false); clip.isMulticamClip = vi.fn(async () => false); clip.isOffline = vi.fn(async () => false); clip.getContentType = vi.fn(async () => 2); root.children?.push(bin); bin.children?.push(clip); let markerCounter = 2; const makeMarker = (guid: string, name: string, startSeconds = 1) => { const state = { name, type: "Comment", comments: "", color: 0, start: startSeconds, duration: 0 }; return { guid, getName: vi.fn(async () => state.name), getType: vi.fn(async () => state.type), getComments: vi.fn(async () => state.comments), getColorIndex: vi.fn(async () => state.color), getStart: vi.fn(async () => ({ seconds: state.start })), getDuration: vi.fn(async () => ({ seconds: state.duration })), createSetNameAction: vi.fn((value: string) => ({ apply: () => { state.name = value; } })), createSetTypeAction: vi.fn((value: string) => ({ apply: () => { state.type = value; } })), createSetCommentsAction: vi.fn((value: string) => ({ apply: () => { state.comments = value; } })), createSetDurationAction: vi.fn((value: { seconds: number }) => ({ apply: () => { state.duration = value.seconds; } })), createSetColorByIndexAction: vi.fn((value: number) => ({ apply: () => { state.color = value; } })), state, }; }; const markerValues = [makeMarker("marker-1", "Beat")]; const markers = { getMarkers: vi.fn(() => markerValues), createAddMarkerAction: vi.fn((name: string, type: string, start: { seconds: number }, duration: { seconds: number }, comments: string) => ({ apply: () => { const marker = makeMarker(`marker-${markerCounter++}`, name, start.seconds); marker.state.type = type; marker.state.duration = duration.seconds; marker.state.comments = comments; markerValues.push(marker); } })), createMoveMarkerAction: vi.fn((marker: ReturnType, time: { seconds: number }) => ({ apply: () => { marker.state.start = time.seconds; } })), createRemoveMarkerAction: vi.fn((marker: ReturnType) => ({ apply: () => markerValues.splice(markerValues.indexOf(marker), 1) })), }; const parameterState: { value: unknown; varying: boolean; keyframes: number[] } = { value: 50, varying: false, keyframes: [] }; const parameterKeyframe = (seconds: number) => ({ position: { seconds }, getTemporalInterpolationMode: vi.fn(async () => 1), }); const parameter = { displayName: "Opacity", areKeyframesSupported: vi.fn(async () => true), isTimeVarying: vi.fn(() => parameterState.varying), getKeyframeListAsTickTimes: vi.fn(() => parameterState.keyframes.map((seconds) => ({ seconds }))), getStartValue: vi.fn(async () => ({ value: parameterState.value })), getValueAtTime: vi.fn(async () => parameterState.value), getKeyframePtr: vi.fn((time: { seconds: number }) => parameterState.keyframes.includes(time.seconds) ? parameterKeyframe(time.seconds) : null), findNextKeyframe: vi.fn((time: { seconds: number }) => { const next = parameterState.keyframes.find((seconds) => seconds > time.seconds); return next == null ? null : parameterKeyframe(next); }), findPreviousKeyframe: vi.fn((time: { seconds: number }) => { const previous = parameterState.keyframes.filter((seconds) => seconds < time.seconds).at(-1); return previous == null ? null : parameterKeyframe(previous); }), findNearestKeyframe: vi.fn((inTime: { seconds: number }, outTime: { seconds: number }) => { const result = parameterState.keyframes.find((seconds) => seconds >= inTime.seconds && seconds <= outTime.seconds); return result == null ? null : parameterKeyframe(result); }), createKeyframe: vi.fn((value: unknown) => ({ value, position: null as { seconds: number } | null })), createSetValueAction: vi.fn((keyframe: { value: unknown }) => ({ apply: () => { parameterState.value = keyframe.value; } })), createSetTimeVaryingAction: vi.fn((value: boolean) => ({ apply: () => { parameterState.varying = value; } })), createAddKeyframeAction: vi.fn((keyframe: { position: { seconds: number } }) => ({ apply: () => parameterState.keyframes.push(keyframe.position.seconds) })), createRemoveKeyframeAction: vi.fn((time: { seconds: number }) => ({ apply: () => { parameterState.keyframes = parameterState.keyframes.filter((value) => value !== time.seconds); } })), createRemoveKeyframeRangeAction: vi.fn((start: { seconds: number }, end: { seconds: number }) => ({ apply: () => { parameterState.keyframes = parameterState.keyframes.filter((value) => value < start.seconds || value > end.seconds); } })), createSetInterpolationAtKeyframeAction: vi.fn(() => ({ apply: () => undefined })), }; const component = { getMatchName: vi.fn(async () => "ADBE Opacity"), getDisplayName: vi.fn(async () => "Opacity"), getParamCount: vi.fn(() => 1), getParam: vi.fn(() => parameter), }; const chain = { getComponentCount: vi.fn(() => 1), getComponentAtIndex: vi.fn(() => component) }; const trackState = { name: "Interview V", start: 10, end: 20, inPoint: 0, outPoint: 10, disabled: false }; const trackItem = { getComponentChain: vi.fn(async () => chain), getProjectItem: vi.fn(async () => clip), getName: vi.fn(async () => trackState.name), getStartTime: vi.fn(async () => ({ seconds: trackState.start })), getEndTime: vi.fn(async () => ({ seconds: trackState.end })), getInPoint: vi.fn(async () => ({ seconds: trackState.inPoint })), getOutPoint: vi.fn(async () => ({ seconds: trackState.outPoint })), getDuration: vi.fn(async () => ({ seconds: trackState.end - trackState.start })), getSpeed: vi.fn(async () => 1), isSpeedReversed: vi.fn(async () => false), isAdjustmentLayer: vi.fn(async () => false), isDisabled: vi.fn(async () => trackState.disabled), createMoveAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.start += time.seconds; trackState.end += time.seconds; } })), createSetStartAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.start = time.seconds; } })), createSetEndAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.end = time.seconds; } })), createSetInPointAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.inPoint = time.seconds; } })), createSetOutPointAction: vi.fn((time: { seconds: number }) => ({ apply: () => { trackState.outPoint = time.seconds; } })), createSetDisabledAction: vi.fn((value: boolean) => ({ apply: () => { trackState.disabled = value; } })), createSetNameAction: vi.fn((value: string) => ({ apply: () => { trackState.name = value; } })), }; const settingsState = { maximumBitDepth: false, maxRenderQuality: false, compositeInLinearColor: false, audioSampleRate: 48000, videoFrameRate: 23.976, videoFieldType: 0, videoPixelAspectRatio: "square", editingMode: "custom", previewFileFormat: "mpeg", previewCodec: "i-frame", videoWidth: 1280, videoHeight: 720, audioDisplayFormat: 1, videoDisplayFormat: 20, }; const displayFormatCandidate: { audio?: number; video?: number } = {}; const settings = { getMaximumBitDepth: vi.fn(async () => settingsState.maximumBitDepth), getMaxRenderQuality: vi.fn(async () => settingsState.maxRenderQuality), getCompositeInLinearColor: vi.fn(async () => settingsState.compositeInLinearColor), getAudioChannelCount: vi.fn(async () => 2), getAudioChannelType: vi.fn(async () => 1), getAudioDisplayFormat: vi.fn(async () => ({ type: settingsState.audioDisplayFormat })), getAudioSampleRate: vi.fn(async () => ({ value: settingsState.audioSampleRate })), getVideoDisplayFormat: vi.fn(async () => ({ type: settingsState.videoDisplayFormat })), getVideoFrameRate: vi.fn(() => ({ value: settingsState.videoFrameRate })), getVideoFieldType: vi.fn(async () => settingsState.videoFieldType), getVideoPixelAspectRatio: vi.fn(async () => settingsState.videoPixelAspectRatio), getEditingMode: vi.fn(async () => settingsState.editingMode), getPreviewFileFormat: vi.fn(async () => settingsState.previewFileFormat), getPreviewCodec: vi.fn(async () => settingsState.previewCodec), getVideoFrameRect: vi.fn(async () => ({ width: settingsState.videoWidth, height: settingsState.videoHeight })), getPreviewFrameRect: vi.fn(async () => ({ width: 640, height: 360 })), setMaximumBitDepth: vi.fn(async (value: boolean) => { settingsState.maximumBitDepth = value; return true; }), setMaxRenderQuality: vi.fn(async (value: boolean) => { settingsState.maxRenderQuality = value; return true; }), setCompositeInLinearColor: vi.fn(async (value: boolean) => { settingsState.compositeInLinearColor = value; return true; }), setAudioDisplayFormat: vi.fn(async (value: { type: number }) => { displayFormatCandidate.audio = value.type; return true; }), setAudioSampleRate: vi.fn(async (value: { value: number }) => { settingsState.audioSampleRate = value.value; return true; }), setVideoDisplayFormat: vi.fn(async (value: { type: number }) => { displayFormatCandidate.video = value.type; return true; }), setVideoFrameRate: vi.fn((value: { value: number }) => { settingsState.videoFrameRate = value.value; return true; }), setVideoFieldType: vi.fn(async (value: number) => { settingsState.videoFieldType = value; return true; }), setVideoPixelAspectRatio: vi.fn(async (value: string) => { settingsState.videoPixelAspectRatio = value; return true; }), setEditingMode: vi.fn(async (value: string) => { settingsState.editingMode = value; return true; }), setPreviewFileFormat: vi.fn(async (value: string) => { settingsState.previewFileFormat = value; return true; }), setPreviewCodec: vi.fn(async (value: string) => { settingsState.previewCodec = value; return true; }), setVideoFrameRect: vi.fn(async (value: { width: number; height: number }) => { settingsState.videoWidth = value.width; settingsState.videoHeight = value.height; return true; }), }; const selection = { getTrackItems: vi.fn(async () => [trackItem]) }; const sequence = { guid: "sequence-1", name: "Assembly", getSelection: vi.fn(async () => selection), getVideoTrackCount: vi.fn(async () => 1), getVideoTrack: vi.fn(async () => ({ getTrackItems: vi.fn(async () => [trackItem]) })), getAudioTrackCount: vi.fn(async () => 1), getAudioTrack: vi.fn(async () => ({ getTrackItems: vi.fn(async () => [trackItem]) })), getSettings: vi.fn(async () => settings), createSetSettingsAction: vi.fn(() => ({ apply: () => { if (displayFormatCandidate.audio !== undefined) settingsState.audioDisplayFormat = displayFormatCandidate.audio; if (displayFormatCandidate.video !== undefined) settingsState.videoDisplayFormat = displayFormatCandidate.video; delete displayFormatCandidate.audio; delete displayFormatCandidate.video; } })), createCloneAction: vi.fn(() => ({ apply: () => sequences.push({ guid: "sequence-2", name: "Assembly Copy" }) })), createSubsequence: vi.fn(async () => ({ guid: "sequence-3", name: "Assembly Subsequence" })), }; clip.getSequence = vi.fn(async () => sequence); const sequences: Array<{ guid: string; name: string }> = [sequence]; const addAction = vi.fn((action: { apply?: () => void }) => { action.apply?.(); return true; }); const project = { guid: "project-1", name: "Documentary", getActiveSequence: vi.fn(async () => sequence), getSequences: vi.fn(async () => sequences), getRootItem: vi.fn(async () => root), lockedAccess: vi.fn((callback: () => void) => callback()), executeTransaction: vi.fn((callback: (compound: { addAction: typeof addAction }) => void) => { callback({ addAction }); return true; }), importFiles: vi.fn(async (paths: string[], _suppress: boolean, target?: MutableItem) => { const parent = target || root; for (const path of paths) parent.children?.push(makeItem(`import-${nextItem++}`, path.split("/").at(-1) || path, { isClip: true, parent })); return true; }), importSequences: vi.fn(async () => true), importAEComps: vi.fn(async () => true), importAllAEComps: vi.fn(async () => true), createSequence: vi.fn(async (name: string) => { const created = { guid: `sequence-empty-${sequences.length}`, name }; sequences.push(created); return created; }), createSequenceFromMedia: vi.fn(async (name: string) => ({ guid: "sequence-created", name })), setActiveSequence: vi.fn(async () => true), openSequence: vi.fn(async () => true), closeSequence: vi.fn(async () => true), deleteSequence: vi.fn(async (target: { guid: string }) => { const index = sequences.indexOf(target); if (index >= 0) sequences.splice(index, 1); return true; }), }; const editor = { createInsertProjectItemAction: vi.fn(() => ({ apply: () => undefined })), createOverwriteItemAction: vi.fn(() => ({ apply: () => undefined })), createCloneTrackItemAction: vi.fn(() => ({ apply: () => undefined })), createRemoveItemsAction: vi.fn(() => ({ apply: () => undefined })), insertMogrtFromPath: vi.fn(() => [trackItem]), insertMogrtFromLibrary: vi.fn(() => [trackItem]), }; const manager = { isAMEInstalled: true, exportSequence: vi.fn(async () => true), encodeProjectItem: vi.fn(async () => true), encodeFile: vi.fn(async () => true), }; const ppro = { Project: { getActiveProject: vi.fn(async () => project) }, ProjectUtils: { getSelection: vi.fn(async () => ({ getItems: vi.fn(async () => [clip]) })), getProjectViewIds: vi.fn(async () => ["view-1"]), getProjectFromViewId: vi.fn(async () => project), getSelectionFromViewId: vi.fn(async () => ({ getItems: vi.fn(async () => [clip]) })), }, Markers: { getMarkers: vi.fn(async () => markers) }, Marker: { MARKER_TYPE_COMMENT: "Comment" }, FolderItem: { cast: vi.fn((item: MutableItem) => { if (!item.isFolder) throw new Error("not folder"); return item; }) }, ClipProjectItem: { cast: vi.fn((item: MutableItem) => { if (!item.isClip) throw new Error("not clip"); return item; }) }, TickTime: { createWithSeconds: vi.fn((seconds: number) => ({ seconds })) }, PointF: class PointF { constructor(readonly x: number, readonly y: number) {} distanceTo(point: { x: number; y: number }) { return Math.hypot(this.x - point.x, this.y - point.y); } }, Color: class Color { constructor(readonly red: number, readonly green: number, readonly blue: number, readonly alpha: number) {} }, FrameRate: { createWithValue: vi.fn((value: number) => ({ value })) }, RectF: class RectF { width = 0; height = 0; }, Guid: { fromString: vi.fn((value: string) => value) }, Constants: { TrackItemType: { CLIP: 1 }, MediaType: { ANY: 0, VIDEO: 1, AUDIO: 2 }, ContentType: { ANY: 0, SEQUENCE: 1, MEDIA: 2 }, InterpolationMode: { LINEAR: 1, HOLD: 2, BEZIER: 3, TIME: 4 }, ExportType: { QUEUE_TO_AME: "ame", QUEUE_TO_APP: "app", IMMEDIATELY: "now" }, }, SequenceEditor: { getEditor: vi.fn(() => editor) }, EncoderManager: { getManager: vi.fn(() => manager), getExportFileExtension: vi.fn(async () => "mp4"), EXPORT_QUEUE_TO_AME: "ame", EXPORT_QUEUE_TO_APP: "app", EXPORT_IMMEDIATELY: "now", }, SequenceSettings: { AUDIO_DISPLAY_FORMAT_SAMPLE_RATE: 1, AUDIO_DISPLAY_FORMAT_MILISECONDS: 2, VIDEO_DISPLAY_FORMAT_23976: 20, VIDEO_DISPLAY_FORMAT_25: 21, VIDEO_DISPLAY_FORMAT_2997: 22, VIDEO_DISPLAY_FORMAT_2997_NON_DROP: 23, VIDEO_DISPLAY_FORMAT_16mm: 24, VIDEO_DISPLAY_FORMAT_35mm: 25, VIDEO_DISPLAY_FORMAT_FRAMES: 26, }, Utils: { isAEInstalled: vi.fn(async () => true) }, }; const workspace = { status: vi.fn(() => ({ configured: true, accessMode: "request", rootName: "Approved", persistent: true, pathDisclosure: "redacted", canonicalPathValidation: "available" })), assertPathAllowed: vi.fn((path: string) => path.replace(/\\/g, "/")), }; const events = Events.createEventJournal({ capacity: 16 }); return { registry: Commands.createCommandRegistry({ ppro, Protocol, workspace, events }), project, ppro, workspace, markers, markerValues, root, bin, clip, sequence, sequences, settings, settingsState, parameter, parameterState, trackState, trackItem, editor, manager, events, }; } describe("advanced stable Premiere UXP workflows", () => { it("advertises all ten groups from runtime probes and labels filesystem boundaries", async () => { const value = advancedHost(); const capabilities = await value.registry.capabilities(); expect(Object.keys(capabilities.commands)).toEqual(expect.arrayContaining([ "projectSelection.views", "projectSelection.inspect", "projectTree.inspect", "markers.inspect", "markers.add", "markers.addBeatGrid", "markers.removeMany", "bins.inspect", "bins.create", "sequenceSettings.get", "sequenceSettings.update", "sequence.displayFormat.inspect", "sequence.displayFormat.update", "project.import", "parameters.inspect", "parameters.point.inspect", "parameters.point.set", "parameters.keyframeAdd", "parameters.keyframe.inspect", "parameters.timeVarying.inspect", "parameters.timeVarying.set", "trackItem.inspect", "trackItem.update", "timeline.insert", "timeline.mogrtPath", "timeline.structure.inspect", "sequences.inspect", "sequences.createEmpty", "trackItem.splitEdit", "sequences.clone", "encoder.preflight", "encoder.sequence", "encoder.file", ])); expect(capabilities.commands["projectSelection.inspect"]).toMatchObject({ supported: true, readOnly: true }); expect(capabilities.commands["markers.add"]).toMatchObject({ supported: true, destructive: true, undoable: true }); expect(capabilities.commands["markers.addBeatGrid"]).toMatchObject({ supported: true, destructive: true, undoable: true }); expect(capabilities.commands["markers.removeMany"]).toMatchObject({ supported: true, destructive: true, undoable: true }); expect(capabilities.commands["project.import"]).toMatchObject({ supported: true, workspaceRequired: true, undoable: false }); expect(capabilities.commands["encoder.sequence"]).toMatchObject({ supported: true, workspaceRequired: true, undoable: false }); expect(capabilities.commands["sequence.displayFormat.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false }); expect(capabilities.commands["sequence.displayFormat.update"]).toMatchObject({ supported: true, destructive: true, undoable: true, idempotent: true }); expect(capabilities.commands["parameters.keyframe.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false }); expect(capabilities.commands["parameters.point.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false, minHostVersion: "26.3.0" }); expect(capabilities.commands["parameters.point.displacement.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false, minHostVersion: "26.3.0" }); expect(capabilities.commands["parameters.point.set"]).toMatchObject({ supported: true, destructive: true, undoable: true, idempotent: true, minHostVersion: "26.3.0" }); expect(capabilities.commands["parameters.timeVarying.inspect"]).toMatchObject({ supported: true, readOnly: true, destructive: false }); expect(capabilities.commands["parameters.timeVarying.set"]).toMatchObject({ supported: true, destructive: true, undoable: true, idempotent: true }); expect(capabilities.commands["sequences.createEmpty"]).toMatchObject({ supported: true, destructive: true, undoable: false, idempotent: true, minHostVersion: "26.3.0" }); }); it("uses Project-view selection and completes marker/bin actions with identity and field readback", async () => { const value = advancedHost(); await expect(value.registry.dispatch("projectSelection.views", {})).resolves.toMatchObject({ count: 1, views: [{ viewId: "view-1", projectId: "project-1", projectName: "Documentary" }], }); await expect(value.registry.dispatch("projectSelection.inspect", { viewId: "view-1" })).resolves.toMatchObject({ count: 1, items: [{ id: "clip-1", name: "Interview.mov" }], resolver: "project_view_selection", }); await expect(value.registry.dispatch("markers.add", { name: "Turn", startSeconds: 3, comments: "Cut here", operationId: "marker-add", })).resolves.toMatchObject({ added: true, outcome: "verified", marker: { name: "Turn", startSeconds: 3 } }); await expect(value.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1, 2, 3], offsetSeconds: 0.5, namePrefix: "Beat", comments: "Detected grid", operationId: "beat-grid", })).resolves.toMatchObject({ added: 3, outcome: "verified", verificationBoundary: "beat_marker_guid_and_time_readback", markers: [ { name: "Beat 1", startSeconds: 1.5 }, { name: "Beat 2", startSeconds: 2.5 }, { name: "Beat 3", startSeconds: 3.5 }, ], }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(2); await expect(value.registry.dispatch("markers.update", { markerGuid: "marker-1", expectedName: "Beat", name: "Beat updated", startSeconds: 2, colorIndex: 4, operationId: "marker-update", })).resolves.toMatchObject({ updated: true, outcome: "verified", marker: { guid: "marker-1", name: "Beat updated", startSeconds: 2, colorIndex: 4 }, }); await expect(value.registry.dispatch("bins.create", { parentBinId: "bin-1", name: "Selects", operationId: "bin-create", })).resolves.toMatchObject({ created: true, outcome: "verified", item: { name: "Selects" } }); }); it("walks the native Project root in deterministic bounded order without reading beyond the requested tree", async () => { const value = advancedHost(); await expect(value.registry.dispatch("projectTree.inspect", { maxItems: 2, maxDepth: 2 })).resolves.toMatchObject({ root: { id: "root", name: "Root", parentId: null, depth: 0, isBin: true }, count: 2, items: [ { id: "bin-1", name: "Rushes", parentId: "root", depth: 1, isBin: true }, { id: "clip-1", name: "Interview.mov", parentId: "bin-1", depth: 2, isBin: false }, ], truncated: false, itemLimitReached: false, depthLimitApplied: false, verificationBoundary: "bounded_project_tree_item_readback", }); expect(value.project.executeTransaction).not.toHaveBeenCalled(); const limited = advancedHost(); limited.bin.getItems.mockClear(); await expect(limited.registry.dispatch("projectTree.inspect", { maxItems: 1, maxDepth: 2 })).resolves.toMatchObject({ count: 1, items: [{ id: "bin-1" }], truncated: true, itemLimitReached: true, depthLimitApplied: false, }); expect(limited.bin.getItems).not.toHaveBeenCalled(); const depthLimited = advancedHost(); depthLimited.root.getItems.mockClear(); await expect(depthLimited.registry.dispatch("projectTree.inspect", { maxDepth: 0 })).resolves.toMatchObject({ count: 0, truncated: true, itemLimitReached: false, depthLimitApplied: true, }); expect(depthLimited.root.getItems).not.toHaveBeenCalled(); await expect(value.registry.dispatch("projectTree.inspect", { maxItems: 513 })).rejects.toThrow(/maxItems must be an integer from 1 to 512/); await expect(value.registry.dispatch("projectTree.inspect", { unexpected: true })).rejects.toThrow(/Unknown argument/); }); it("removes only explicit reviewed marker snapshots in one transaction and replays the operation id", async () => { const value = advancedHost(); await expect(value.registry.dispatch("markers.add", { name: "Remove", startSeconds: 4, durationSeconds: 1.5, operationId: "marker-batch-fixture", })).resolves.toMatchObject({ added: true, marker: { guid: "marker-2" } }); const transactionCount = value.project.executeTransaction.mock.calls.length; const args = { confirmDestructive: true, markerSnapshots: [ { markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }, { markerGuid: "marker-2", expectedName: "Remove", expectedStartSeconds: 4, expectedDurationSeconds: 1.5 }, ], operationId: "marker-batch-remove", }; await expect(value.registry.dispatch("markers.removeMany", args)).resolves.toMatchObject({ requested: 2, removed: 2, markerGuids: ["marker-1", "marker-2"], remainingTargetGuids: [], remainingCount: 0, outcome: "verified", verified: true, verificationBoundary: "marker_guid_absence_readback", }); await expect(value.registry.dispatch("markers.removeMany", args)).resolves.toMatchObject({ requested: 2, removed: 2, replayed: true, }); expect(value.markers.createRemoveMarkerAction).toHaveBeenCalledTimes(2); expect(value.project.executeTransaction).toHaveBeenCalledTimes(transactionCount + 1); }); it("rejects unconfirmed, duplicate, and stale marker batches before creating actions", async () => { const value = advancedHost(); const snapshot = { markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 0, expectedDurationSeconds: 0 }; await expect(value.registry.dispatch("markers.removeMany", { markerSnapshots: [snapshot] })) .rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); await expect(value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: [snapshot, snapshot], })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: [{ ...snapshot, expectedName: "Changed elsewhere" }], })).rejects.toMatchObject({ code: "UXP_STALE_MARKER" }); expect(value.markers.createRemoveMarkerAction).not.toHaveBeenCalled(); expect(value.project.lockedAccess).not.toHaveBeenCalled(); expect(value.project.executeTransaction).not.toHaveBeenCalled(); }); it("validates marker batch confirmation and bounds before resolving Premiere state", async () => { const value = advancedHost(); const snapshot = { markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }; await expect(value.registry.dispatch("markers.removeMany", { markerSnapshots: [snapshot] })) .rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); await expect(value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: Array.from({ length: 129 }, () => snapshot), })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); expect(value.ppro.Project.getActiveProject).not.toHaveBeenCalled(); expect(value.ppro.Markers.getMarkers).not.toHaveBeenCalled(); }); it("keeps batch-only marker arguments scoped to removeMany", async () => { const value = advancedHost(); await expect(value.registry.dispatch("markers.add", { name: "Unexpected", confirmDestructive: true })) .rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("markers.update", { markerGuid: "marker-1", name: "Unexpected", markerSnapshots: [], })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("markers.remove", { markerGuid: "marker-1", confirmDestructive: true })) .rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); expect(value.ppro.Project.getActiveProject).not.toHaveBeenCalled(); expect(value.ppro.Markers.getMarkers).not.toHaveBeenCalled(); }); it("serializes marker updates behind a batch removal snapshot and commit", async () => { const value = advancedHost(); let releaseNameRead: () => void = () => undefined; const nameRead = new Promise((resolve) => { releaseNameRead = resolve; }); const marker = value.markerValues[0]; marker.getName.mockImplementationOnce(async () => { await nameRead; return marker.state.name; }); const removing = value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }], }); await Promise.resolve(); const updating = value.registry.dispatch("markers.update", { markerGuid: "marker-1", name: "Changed concurrently" }); await Promise.resolve(); expect(marker.createSetNameAction).not.toHaveBeenCalled(); releaseNameRead(); await expect(removing).resolves.toMatchObject({ outcome: "verified", removed: 1 }); await expect(updating).rejects.toMatchObject({ code: "UXP_TARGET_NOT_FOUND" }); expect(marker.createSetNameAction).not.toHaveBeenCalled(); }); it("reports marker batch deletion as committed-unverified when GUID absence cannot be read back", async () => { const value = advancedHost(); value.markers.createRemoveMarkerAction.mockImplementation(() => ({ apply: () => undefined })); await expect(value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }], })).resolves.toMatchObject({ requested: 1, removed: null, remainingTargetGuids: ["marker-1"], outcome: "committed_unverified", verified: false, verificationBoundary: "marker_guid_absence_readback", }); }); it("reads removal fields only for requested markers and confirms absence by GUID without unrelated getters", async () => { const value = advancedHost(); const target = value.markerValues[0]; const unrelated = { ...target, guid: "marker-unrelated", getName: vi.fn(async () => { throw new Error("unrelated name getter"); }), getType: vi.fn(async () => { throw new Error("unrelated type getter"); }), getComments: vi.fn(async () => { throw new Error("unrelated comments getter"); }), getColorIndex: vi.fn(async () => { throw new Error("unrelated color getter"); }), getStart: vi.fn(async () => { throw new Error("unrelated start getter"); }), getDuration: vi.fn(async () => { throw new Error("unrelated duration getter"); }), }; value.markerValues.push(unrelated); await expect(value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }], })).resolves.toMatchObject({ requested: 1, removed: 1, remainingCount: 1, remainingTargetGuids: [], outcome: "verified", }); expect(target.getName).toHaveBeenCalledOnce(); expect(target.getStart).toHaveBeenCalledOnce(); expect(target.getDuration).toHaveBeenCalledOnce(); expect(unrelated.getName).not.toHaveBeenCalled(); expect(unrelated.getType).not.toHaveBeenCalled(); expect(unrelated.getComments).not.toHaveBeenCalled(); expect(unrelated.getColorIndex).not.toHaveBeenCalled(); expect(unrelated.getStart).not.toHaveBeenCalled(); expect(unrelated.getDuration).not.toHaveBeenCalled(); }); it("serializes marker update and batch deletion by owner so changed snapshots fail stale without deletion", async () => { const value = advancedHost(); const marker = value.markerValues[0]; let releaseUpdate = () => undefined; let enteredUpdate = () => undefined; const updateGate = new Promise((resolve) => { releaseUpdate = resolve; }); const updateEntered = new Promise((resolve) => { enteredUpdate = resolve; }); let enteredRemovalContext = () => undefined; const removalContextEntered = new Promise((resolve) => { enteredRemovalContext = resolve; }); let markerCollectionRequests = 0; value.ppro.Markers.getMarkers.mockImplementation(async () => { markerCollectionRequests += 1; if (markerCollectionRequests === 2) enteredRemovalContext(); return value.markers; }); let pauseFirstNameRead = true; marker.getName.mockImplementation(async () => { if (pauseFirstNameRead) { pauseFirstNameRead = false; enteredUpdate(); await updateGate; } return marker.state.name; }); const update = value.registry.dispatch("markers.update", { markerGuid: "marker-1", expectedName: "Beat", name: "Renamed", operationId: "marker-update-concurrent", }); await updateEntered; const removal = value.registry.dispatch("markers.removeMany", { confirmDestructive: true, markerSnapshots: [{ markerGuid: "marker-1", expectedName: "Beat", expectedStartSeconds: 1, expectedDurationSeconds: 0 }], operationId: "marker-remove-concurrent", }); await removalContextEntered; await Promise.resolve(); await Promise.resolve(); expect(marker.getName).toHaveBeenCalledTimes(1); releaseUpdate(); await expect(update).resolves.toMatchObject({ updated: true, outcome: "verified", marker: { name: "Renamed" } }); await expect(removal).rejects.toMatchObject({ code: "UXP_STALE_MARKER" }); expect(value.markers.createRemoveMarkerAction).not.toHaveBeenCalled(); expect(value.markerValues).toHaveLength(1); expect(value.markerValues[0].state.name).toBe("Renamed"); }); it("creates a single-source silence-cut stringout without mutating an existing sequence", async () => { const value = advancedHost(); await expect(value.registry.dispatch("silence.deriveSequence", { sourceProjectItemId: "clip-1", name: "Interview Tight", confirmNonUndoable: true, operationId: "silence-stringout", keepRanges: [ { startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }, { startSeconds: 4, endSeconds: 10, startFrame: 120, endFrame: 300 }, ], })).resolves.toMatchObject({ created: true, partial: false, outcome: "committed_unverified", originalChanged: false, createdSubclips: [{ name: "Interview Tight Keep 1" }, { name: "Interview Tight Keep 2" }], insertedProjectItemIds: expect.arrayContaining([expect.stringMatching(/^subclip-/)]), sequence: { name: "Interview Tight" }, }); expect(value.clip.createSubClipAction).toHaveBeenCalledTimes(2); expect(value.project.createSequenceFromMedia).toHaveBeenCalledTimes(1); expect(value.editor.createInsertProjectItemAction).toHaveBeenCalledTimes(1); expect(value.sequence.createCloneAction).not.toHaveBeenCalled(); }); it("caches a partial subclip transaction receipt so retry cannot duplicate artifacts", async () => { const value = advancedHost(); value.clip.createSubClipAction .mockImplementationOnce((name: string) => ({ apply: () => { const created = { ...value.clip, id: "partial-subclip", name, getId: vi.fn(async () => "partial-subclip") }; value.bin.children?.push(created); } })) .mockImplementationOnce(() => ({ apply: () => { throw new Error("host rejected second action"); } })); const args = { sourceProjectItemId: "clip-1", name: "Partial Tight", confirmNonUndoable: true, operationId: "partial-stringout", keepRanges: [ { startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }, { startSeconds: 4, endSeconds: 10, startFrame: 120, endFrame: 300 }, ], }; await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ partial: true, outcome: "committed_unverified", sequence: null, verificationBoundary: "derived_subclip_partial_transaction_receipt", }); await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ partial: true, replayed: true }); expect(value.clip.createSubClipAction).toHaveBeenCalledTimes(2); expect(value.project.createSequenceFromMedia).not.toHaveBeenCalled(); }); it("resolves the destination before creating subclips", async () => { const value = advancedHost(); await expect(value.registry.dispatch("silence.deriveSequence", { sourceProjectItemId: "clip-1", targetBinId: "missing-bin", name: "Invalid Target", confirmNonUndoable: true, operationId: "invalid-target", keepRanges: [ { startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }, ], })).rejects.toThrow("projectItemId was not found"); expect(value.clip.createSubClipAction).not.toHaveBeenCalled(); expect(value.project.createSequenceFromMedia).not.toHaveBeenCalled(); }); it("does not cache failures that occur before a subclip transaction is attempted", async () => { const value = advancedHost(); const implementation = value.clip.createSubClipAction.getMockImplementation(); value.clip.createSubClipAction.mockImplementationOnce(() => { throw new Error("cannot construct action"); }); const args = { sourceProjectItemId: "clip-1", name: "Retry Tight", confirmNonUndoable: true, operationId: "pre-transaction-retry", keepRanges: [ { startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }, ], }; await expect(value.registry.dispatch("silence.deriveSequence", args)).rejects.toThrow("cannot construct action"); value.clip.createSubClipAction.mockImplementation(implementation!); await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ created: true, partial: false, }); expect(value.project.createSequenceFromMedia).toHaveBeenCalledTimes(1); }); it("rejects a silence stringout before overflowing the destination bin", async () => { const value = advancedHost(); value.bin.children?.push(...Array.from({ length: 1023 }, () => value.clip)); await expect(value.registry.dispatch("silence.deriveSequence", { sourceProjectItemId: "clip-1", name: "Over Capacity", confirmNonUndoable: true, operationId: "over-capacity", keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }], })).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(value.clip.createSubClipAction).not.toHaveBeenCalled(); }); it("caches a partial subclip receipt instead of overflowing the sequence collection", async () => { const value = advancedHost(); value.sequences.push(...Array.from({ length: 1023 }, (_, index) => ({ guid: `sequence-${index + 2}`, name: `Existing ${index + 2}` }))); await expect(value.registry.dispatch("silence.deriveSequence", { sourceProjectItemId: "clip-1", name: "Sequence Capacity", confirmNonUndoable: true, operationId: "sequence-capacity", keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }], })).resolves.toMatchObject({ partial: true, verificationBoundary: "derived_sequence_capacity_preflight", sequence: null, }); expect(value.project.createSequenceFromMedia).not.toHaveBeenCalled(); }); it("caches a partial receipt when a post-mutation subclip snapshot rejects", async () => { const value = advancedHost(); let idReads = 0; value.clip.createSubClipAction.mockImplementationOnce((name: string) => ({ apply: () => { const created = { ...value.clip, id: "subclip-snapshot-failure", name, getId: vi.fn(async () => { idReads += 1; if (idReads === 1) return "subclip-snapshot-failure"; throw new Error("snapshot unavailable"); }), }; value.bin.children?.push(created); } })); value.ppro.SequenceEditor.getEditor.mockImplementationOnce(() => { throw new Error("editor unavailable"); }); const args = { sourceProjectItemId: "clip-1", name: "Snapshot Tight", confirmNonUndoable: true, operationId: "snapshot-failure", keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }], }; await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ partial: true, verificationBoundary: "derived_sequence_partial_insert_receipt", createdSubclips: [{ id: "", name: "Snapshot Tight Keep 1" }], }); await expect(value.registry.dispatch("silence.deriveSequence", args)).resolves.toMatchObject({ partial: true, replayed: true }); expect(value.clip.createSubClipAction).toHaveBeenCalledTimes(1); }); it("reconciles a sequence created before its host call rejects", async () => { const value = advancedHost(); value.project.createSequenceFromMedia.mockImplementationOnce(async (name: string) => { value.sequences.push({ guid: "sequence-after-error", name }); throw new Error("host rejected after creation"); }); await expect(value.registry.dispatch("silence.deriveSequence", { sourceProjectItemId: "clip-1", name: "Recovered Tight", confirmNonUndoable: true, operationId: "sequence-reconcile", keepRanges: [{ startSeconds: 0, endSeconds: 2, startFrame: 0, endFrame: 60 }], })).resolves.toMatchObject({ partial: true, verificationBoundary: "derived_sequence_host_reconciliation", sequence: { id: "sequence-after-error", name: "Recovered Tight" }, }); }); it("fails the silence-stringout capability probe when required host primitives are absent", async () => { const value = advancedHost(); value.ppro.TickTime.createWithSeconds = undefined; const capabilities = await value.registry.capabilities(); expect(capabilities.commands["silence.deriveSequence"]).toMatchObject({ supported: false }); }); it("rejects invalid beat grids before mutation and reports contradictory readback as unverified", async () => { const value = advancedHost(); for (const args of [ { beatTimesSeconds: [2, 1] }, { beatTimesSeconds: [1, 1] }, { beatTimesSeconds: [1], offsetSeconds: -2 }, ]) { await expect(value.registry.dispatch("markers.addBeatGrid", args)).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); } expect(value.project.lockedAccess).not.toHaveBeenCalled(); value.markerValues.push(...Array.from({ length: 2046 }, () => value.markerValues[0])); await expect(value.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1, 2] })) .rejects.toMatchObject({ code: "UXP_COLLECTION_LIMIT" }); expect(value.project.lockedAccess).not.toHaveBeenCalled(); const emptyGuid = advancedHost(); emptyGuid.markers.createAddMarkerAction.mockImplementation((name: string, type: string, start: { seconds: number }) => ({ apply: () => { emptyGuid.markerValues.push({ ...emptyGuid.markerValues[0], guid: "", getName: vi.fn(async () => name), getType: vi.fn(async () => type), getStart: vi.fn(async () => ({ seconds: start.seconds })), }); } })); await expect(emptyGuid.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1] })) .resolves.toMatchObject({ outcome: "committed_unverified", verified: false }); const wrongTime = advancedHost(); wrongTime.markers.createAddMarkerAction.mockImplementation((name: string, type: string, start: { seconds: number }) => ({ apply: () => { wrongTime.markerValues.push({ ...wrongTime.markerValues[0], guid: "wrong-time-guid", getName: vi.fn(async () => name), getType: vi.fn(async () => type), getStart: vi.fn(async () => ({ seconds: start.seconds + 1 })), }); } })); await expect(wrongTime.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1] })) .resolves.toMatchObject({ outcome: "committed_unverified", verified: false }); const wrongName = advancedHost(); wrongName.markers.createAddMarkerAction.mockImplementation((_name: string, type: string, start: { seconds: number }) => ({ apply: () => { wrongName.markerValues.push({ ...wrongName.markerValues[0], guid: "wrong-name-guid", getName: vi.fn(async () => "Unexpected"), getType: vi.fn(async () => type), getStart: vi.fn(async () => ({ seconds: start.seconds })), }); } })); await expect(wrongName.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [1] })) .resolves.toMatchObject({ outcome: "committed_unverified", verified: false }); const missingZeroTime = advancedHost(); missingZeroTime.markers.createAddMarkerAction.mockImplementation((name: string, type: string) => ({ apply: () => { missingZeroTime.markerValues.push({ ...missingZeroTime.markerValues[0], guid: "missing-zero-time-guid", getName: vi.fn(async () => name), getType: vi.fn(async () => type), getStart: vi.fn(async () => ({})), }); } })); await expect(missingZeroTime.registry.dispatch("markers.addBeatGrid", { beatTimesSeconds: [0] })) .resolves.toMatchObject({ outcome: "committed_unverified", verified: false }); expect(missingZeroTime.markerValues[0].getName).not.toHaveBeenCalled(); expect(missingZeroTime.markerValues[0].getStart).not.toHaveBeenCalled(); const getterFailure = advancedHost(); getterFailure.markers.createAddMarkerAction.mockImplementation((name: string, type: string, start: { seconds: number }) => ({ apply: () => { getterFailure.markerValues.push({ ...getterFailure.markerValues[0], guid: "getter-failure-guid", getName: vi.fn(async () => { throw new Error("readback failed"); }), getType: vi.fn(async () => type), getStart: vi.fn(async () => ({ seconds: start.seconds })), }); } })); const getterFailureArgs = { beatTimesSeconds: [1], operationId: "getter-failure-grid" }; await expect(getterFailure.registry.dispatch("markers.addBeatGrid", getterFailureArgs)) .resolves.toMatchObject({ outcome: "committed_unverified", verified: false, added: null, afterCount: null }); await expect(getterFailure.registry.dispatch("markers.addBeatGrid", getterFailureArgs)) .resolves.toMatchObject({ outcome: "committed_unverified", verified: false }); expect(getterFailure.project.executeTransaction).toHaveBeenCalledTimes(1); }); it("updates sequence settings, imports workspace media, and automates a typed effect parameter", async () => { const value = advancedHost(); await expect(value.registry.dispatch("sequenceSettings.update", { updates: { maximumBitDepth: true, videoFrameRate: 24, videoWidth: 1920, videoHeight: 1080 }, operationId: "settings-update", })).resolves.toMatchObject({ updated: true, outcome: "verified", after: { maximumBitDepth: true, videoFrameRate: 24, videoFrame: { width: 1920, height: 1080 } }, }); await expect(value.registry.dispatch("project.import", { mode: "files", paths: ["D:/Approved/broll.mov"], targetBinId: "bin-1", })).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); await expect(value.registry.dispatch("project.import", { mode: "files", paths: ["D:/Approved/broll.mov"], targetBinId: "bin-1", confirmNonUndoable: true, operationId: "import-files", })).resolves.toMatchObject({ imported: true, outcome: "committed_unverified", verified: false, requested: 1, observedAddedCount: 1, addedItemIds: [expect.stringMatching(/^import-/)], }); await expect(value.registry.dispatch("parameters.set", { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity", value: 80, operationId: "parameter-set", })).resolves.toMatchObject({ updated: true, outcome: "verified", after: { value: 80 } }); expect(value.workspace.assertPathAllowed).toHaveBeenCalledWith("D:/Approved/broll.mov", { label: "paths[0]", kind: "file", }); }); it("double-reads a static PointF parameter and applies one reviewed point with serialized readback and replay", async () => { const value = advancedHost(); value.parameterState.value = { x: 960, y: 540 }; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; const inspected = await value.registry.dispatch("parameters.point.inspect", target); expect(inspected).toMatchObject({ projectId: "project-1", sequenceId: "sequence-1", timeVarying: false, point: { x: 960, y: 540 }, componentId: "ADBE Opacity", paramName: "Opacity", }); expect(value.project.executeTransaction).not.toHaveBeenCalled(); const input = { ...target, expectedSnapshot: inspected, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-parameter-replay" }; await expect(value.registry.dispatch("parameters.point.set", input)).resolves.toMatchObject({ operationId: "point-parameter-replay", updated: true, outcome: "verified", before: { point: { x: 960, y: 540 } }, after: { point: { x: 1100, y: 600 } }, verificationBoundary: "point_parameter_readback", }); await expect(value.registry.dispatch("parameters.point.set", input)).resolves.toMatchObject({ replayed: true, after: { point: { x: 1100, y: 600 } } }); expect(value.parameter.createKeyframe).toHaveBeenCalledWith(expect.objectContaining({ x: 1100, y: 600 })); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); }); it("double-reads one animated PointF parameter's native endpoint displacement without mutating Premiere", async () => { const value = advancedHost(); value.parameterState.varying = true; const nativePoints: Array<{ x: number; y: number; distanceTo: ReturnType }> = []; const pointAt = (x: number, y: number) => { const point = { x, y, distanceTo: vi.fn((other: { x: number; y: number }) => Math.hypot(x - other.x, y - other.y)) }; nativePoints.push(point); return point; }; value.parameter.getValueAtTime.mockImplementation(async (time: { seconds: number }) => time.seconds === 2 ? pointAt(0, 0) : pointAt(3, 4)); const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; await expect(value.registry.dispatch("parameters.point.displacement.inspect", { ...target, startSeconds: 2, endSeconds: 5, })).resolves.toMatchObject({ projectId: "project-1", sequenceId: "sequence-1", timeVarying: true, startSeconds: 2, endSeconds: 5, startPoint: { x: 0, y: 0 }, endPoint: { x: 3, y: 4 }, straightLineDistance: 5, }); expect(nativePoints[0].distanceTo).toHaveBeenCalledWith(expect.objectContaining({ x: 3, y: 4 })); expect(value.project.executeTransaction).not.toHaveBeenCalled(); }); it("fails closed when PointF displacement is static, malformed, stale, or has an invalid interval", async () => { const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity", startSeconds: 2, endSeconds: 5 }; const staticPoint = advancedHost(); staticPoint.parameter.getValueAtTime.mockResolvedValue({ x: 0, y: 0, distanceTo: () => 0 }); await expect(staticPoint.registry.dispatch("parameters.point.displacement.inspect", target)).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" }); const malformedPoint = advancedHost(); malformedPoint.parameterState.varying = true; malformedPoint.parameter.getValueAtTime.mockResolvedValue({ x: 0, y: 0 }); await expect(malformedPoint.registry.dispatch("parameters.point.displacement.inspect", target)).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" }); const changing = advancedHost(); changing.parameterState.varying = true; let reads = 0; changing.parameter.getValueAtTime.mockImplementation(async (time: { seconds: number }) => { reads += 1; const endX = reads >= 4 ? 4 : 3; const x = time.seconds === 2 ? 0 : endX; return { x, y: time.seconds === 2 ? 0 : 4, distanceTo: (other: { x: number; y: number }) => Math.hypot(x - other.x, (time.seconds === 2 ? 0 : 4) - other.y) }; }); await expect(changing.registry.dispatch("parameters.point.displacement.inspect", target)).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" }); await expect(changing.registry.dispatch("parameters.point.displacement.inspect", { ...target, endSeconds: 2 })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); }); it("fails closed on stale, animated, unavailable, or changing PointF parameter state", async () => { const value = advancedHost(); value.parameterState.value = { x: 960, y: 540 }; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; const expectedSnapshot = await value.registry.dispatch("parameters.point.inspect", target); value.parameterState.value = { x: 961, y: 540 }; await expect(value.registry.dispatch("parameters.point.set", { ...target, expectedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-stale", })).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" }); expect(value.project.executeTransaction).not.toHaveBeenCalled(); await expect(value.registry.dispatch("parameters.point.set", { ...target, expectedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); expect(value.project.executeTransaction).not.toHaveBeenCalled(); const animated = advancedHost(); animated.parameterState.value = { x: 960, y: 540 }; animated.parameterState.varying = true; const animatedSnapshot = await animated.registry.dispatch("parameters.point.inspect", target); await expect(animated.registry.dispatch("parameters.point.set", { ...target, expectedSnapshot: animatedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-animated", })).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" }); expect(animated.project.executeTransaction).not.toHaveBeenCalled(); const changing = advancedHost(); changing.parameterState.value = { x: 960, y: 540 }; let reads = 0; changing.parameter.getStartValue.mockImplementation(async () => { reads += 1; return { value: reads === 1 ? { x: 960, y: 540 } : { x: 961, y: 540 } }; }); await expect(changing.registry.dispatch("parameters.point.inspect", target)).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" }); const unavailable = advancedHost(); Reflect.deleteProperty(unavailable.ppro, "PointF"); const capabilities = await unavailable.registry.capabilities(); expect(capabilities.commands["parameters.point.inspect"]).toMatchObject({ supported: false }); expect(capabilities.commands["parameters.point.displacement.inspect"]).toMatchObject({ supported: false }); expect(capabilities.commands["parameters.point.set"]).toMatchObject({ supported: false }); }); it("serializes distinct PointF updates so a later stale snapshot cannot overwrite the first", async () => { const value = advancedHost(); value.parameterState.value = { x: 960, y: 540 }; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; const expectedSnapshot = await value.registry.dispatch("parameters.point.inspect", target); let releaseRead: (() => void) | undefined; const firstReadReleased = new Promise((resolve) => { releaseRead = resolve; }); let signalRead: (() => void) | undefined; const firstReadStarted = new Promise((resolve) => { signalRead = resolve; }); let reads = 0; value.parameter.getStartValue.mockImplementation(async () => { reads += 1; if (reads === 1) { signalRead?.(); await firstReadReleased; } return { value: value.parameterState.value }; }); const first = value.registry.dispatch("parameters.point.set", { ...target, expectedSnapshot, point: { x: 1100, y: 600 }, confirmSetPoint: true, operationId: "point-first", }); await firstReadStarted; const second = value.registry.dispatch("parameters.point.set", { ...target, expectedSnapshot, point: { x: 1200, y: 700 }, confirmSetPoint: true, operationId: "point-second", }); releaseRead?.(); await expect(first).resolves.toMatchObject({ outcome: "verified", after: { point: { x: 1100, y: 600 } } }); await expect(second).rejects.toMatchObject({ code: "UXP_STALE_POINT_PARAMETER" }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); expect(value.parameterState.value).toMatchObject({ x: 1100, y: 600 }); }); it("double-reads a static Color parameter and applies one reviewed RGBA value with replay", async () => { const value = advancedHost(); value.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 }; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; const inspected = await value.registry.dispatch("parameters.color.inspect", target); expect(inspected).toMatchObject({ projectId: "project-1", sequenceId: "sequence-1", timeVarying: false, color: { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 }, }); const input = { ...target, expectedSnapshot: inspected, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, confirmSetColor: true, operationId: "color-parameter-replay", }; await expect(value.registry.dispatch("parameters.color.set", input)).resolves.toMatchObject({ operationId: "color-parameter-replay", updated: true, outcome: "verified", before: { color: { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 } }, after: { color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 } }, verificationBoundary: "color_parameter_readback", }); await expect(value.registry.dispatch("parameters.color.set", input)).resolves.toMatchObject({ replayed: true, after: { color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 } }, }); expect(value.parameter.createKeyframe).toHaveBeenCalledWith(expect.objectContaining({ red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 })); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); }); it("fails closed on stale, animated, unavailable, or changing Color parameter state", async () => { const value = advancedHost(); value.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 }; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; const expectedSnapshot = await value.registry.dispatch("parameters.color.inspect", target); value.parameterState.value = { red: 0.2, green: 0.2, blue: 0.3, alpha: 1 }; await expect(value.registry.dispatch("parameters.color.set", { ...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, confirmSetColor: true, operationId: "color-stale", })).rejects.toMatchObject({ code: "UXP_STALE_COLOR_PARAMETER" }); expect(value.project.executeTransaction).not.toHaveBeenCalled(); await expect(value.registry.dispatch("parameters.color.set", { ...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, confirmSetColor: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("parameters.color.set", { ...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, operationId: "color-unconfirmed", })).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); expect(value.project.executeTransaction).not.toHaveBeenCalled(); const animated = advancedHost(); animated.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 }; animated.parameterState.varying = true; const animatedSnapshot = await animated.registry.dispatch("parameters.color.inspect", target); await expect(animated.registry.dispatch("parameters.color.set", { ...target, expectedSnapshot: animatedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, confirmSetColor: true, operationId: "color-animated", })).rejects.toMatchObject({ code: "UXP_TARGET_UNSUPPORTED" }); expect(animated.project.executeTransaction).not.toHaveBeenCalled(); const changing = advancedHost(); changing.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 }; let reads = 0; changing.parameter.getStartValue.mockImplementation(async () => { reads += 1; return { value: reads === 1 ? { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 } : { red: 0.2, green: 0.2, blue: 0.3, alpha: 1 } }; }); await expect(changing.registry.dispatch("parameters.color.inspect", target)).rejects.toMatchObject({ code: "UXP_STALE_COLOR_PARAMETER" }); const unavailable = advancedHost(); Reflect.deleteProperty(unavailable.ppro, "Color"); const capabilities = await unavailable.registry.capabilities(); expect(capabilities.commands["parameters.color.inspect"]).toMatchObject({ supported: false }); expect(capabilities.commands["parameters.color.set"]).toMatchObject({ supported: false }); }); it("serializes distinct Color updates so a later stale snapshot cannot overwrite the first", async () => { const value = advancedHost(); value.parameterState.value = { red: 0.1, green: 0.2, blue: 0.3, alpha: 1 }; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; const expectedSnapshot = await value.registry.dispatch("parameters.color.inspect", target); let releaseRead: (() => void) | undefined; const firstReadReleased = new Promise((resolve) => { releaseRead = resolve; }); let signalRead: (() => void) | undefined; const firstReadStarted = new Promise((resolve) => { signalRead = resolve; }); let reads = 0; value.parameter.getStartValue.mockImplementation(async () => { reads += 1; if (reads === 1) { signalRead?.(); await firstReadReleased; } return { value: value.parameterState.value }; }); const first = value.registry.dispatch("parameters.color.set", { ...target, expectedSnapshot, color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }, confirmSetColor: true, operationId: "color-first", }); await firstReadStarted; const second = value.registry.dispatch("parameters.color.set", { ...target, expectedSnapshot, color: { red: 0.4, green: 0.5, blue: 0.6, alpha: 0.7 }, confirmSetColor: true, operationId: "color-second", }); releaseRead?.(); await expect(first).resolves.toMatchObject({ outcome: "verified", after: { color: { red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 } } }); await expect(second).rejects.toMatchObject({ code: "UXP_STALE_COLOR_PARAMETER" }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); expect(value.parameterState.value).toMatchObject({ red: 0.9, green: 0.8, blue: 0.7, alpha: 0.6 }); }); it("guards, serializes, replays, and reads back native sequence display-format updates", async () => { const value = advancedHost(); await expect(value.registry.dispatch("sequence.displayFormat.inspect", {})).resolves.toMatchObject({ sequence: { id: "sequence-1" }, displayFormats: { audioDisplayFormat: 1, videoDisplayFormat: 20 }, supportedDisplayFormats: { audio: [{ constant: "AUDIO_DISPLAY_FORMAT_MILISECONDS", code: 2 }, { constant: "AUDIO_DISPLAY_FORMAT_SAMPLE_RATE", code: 1 }], video: expect.arrayContaining([{ constant: "VIDEO_DISPLAY_FORMAT_FRAMES", code: 26 }]), }, }); const update = { expectedSequenceGuid: "sequence-1", expectedDisplayFormats: { audioDisplayFormat: 1, videoDisplayFormat: 20 }, updates: { audioDisplayFormat: 2, videoDisplayFormat: 26 }, operationId: "display-format-replay", }; await expect(value.registry.dispatch("sequence.displayFormat.update", update)).resolves.toMatchObject({ updated: true, outcome: "verified", before: { audioDisplayFormat: 1, videoDisplayFormat: 20 }, after: { audioDisplayFormat: 2, videoDisplayFormat: 26 }, operation: { mutatesProject: true, undo: { supported: true }, cancellation: { supported: false } }, }); await expect(value.registry.dispatch("sequence.displayFormat.update", update)).resolves.toMatchObject({ replayed: true, operationId: "display-format-replay", after: { audioDisplayFormat: 2, videoDisplayFormat: 26 }, }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); await expect(value.registry.dispatch("sequence.displayFormat.update", { expectedSequenceGuid: "sequence-1", expectedDisplayFormats: { audioDisplayFormat: 1, videoDisplayFormat: 26 }, updates: { audioDisplayFormat: 1 }, })).rejects.toMatchObject({ code: "UXP_STALE_SEQUENCE_DISPLAY_FORMAT" }); await expect(value.registry.dispatch("sequence.displayFormat.update", { expectedSequenceGuid: "sequence-1", expectedDisplayFormats: { audioDisplayFormat: 2, videoDisplayFormat: 26 }, updates: { audioDisplayFormat: 999 }, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); const concurrent = advancedHost(); let releaseFirstSnapshot: (() => void) | undefined; const firstSnapshotReleased = new Promise((resolve) => { releaseFirstSnapshot = resolve; }); let signalFirstSnapshot: (() => void) | undefined; const firstSnapshotStarted = new Promise((resolve) => { signalFirstSnapshot = resolve; }); let audioReads = 0; concurrent.settings.getAudioDisplayFormat.mockImplementation(async () => { audioReads += 1; if (audioReads === 1) { signalFirstSnapshot?.(); await firstSnapshotReleased; } return { type: concurrent.settingsState.audioDisplayFormat }; }); const expected = { audioDisplayFormat: 1, videoDisplayFormat: 20 }; const first = concurrent.registry.dispatch("sequence.displayFormat.update", { expectedSequenceGuid: "sequence-1", expectedDisplayFormats: expected, updates: { audioDisplayFormat: 2 }, operationId: "display-format-first", }); await firstSnapshotStarted; const second = concurrent.registry.dispatch("sequence.displayFormat.update", { expectedSequenceGuid: "sequence-1", expectedDisplayFormats: expected, updates: { videoDisplayFormat: 26 }, operationId: "display-format-second", }); releaseFirstSnapshot?.(); await expect(first).resolves.toMatchObject({ outcome: "verified", after: { audioDisplayFormat: 2, videoDisplayFormat: 20 } }); await expect(second).rejects.toMatchObject({ code: "UXP_STALE_SEQUENCE_DISPLAY_FORMAT" }); expect(concurrent.project.executeTransaction).toHaveBeenCalledTimes(1); }); it("verifies relative track moves while keeping SequenceEditor transaction limits explicit", async () => { const value = advancedHost(); await expect(value.registry.dispatch("trackItem.update", { mediaType: "video", trackIndex: 0, clipIndex: 0, moveBySeconds: 2, disabled: true, operationId: "track-move", })).resolves.toMatchObject({ updated: true, outcome: "verified", before: { startSeconds: 10, endSeconds: 20, disabled: false }, after: { startSeconds: 12, endSeconds: 22, disabled: true }, }); await expect(value.registry.dispatch("timeline.insert", { projectItemId: "clip-1", timeSeconds: 5, videoTrackIndex: 0, audioTrackIndex: 0, operationId: "timeline-insert", })).resolves.toMatchObject({ inserted: true, outcome: "committed_unverified", verified: false, verificationBoundary: "sequence_editor_transaction", operation: { mutatesProject: true, undo: { supported: true } }, }); await expect(value.registry.dispatch("timeline.mogrtLibrary", { libraryName: "Brand", elementName: "Lower Third", timeSeconds: 5, videoTrackIndex: 0, audioTrackIndex: 0, confirmNonUndoable: true, })).resolves.toMatchObject({ inserted: 1, source: "library", outcome: "committed_unverified", verified: false, verificationBoundary: "sequence_editor_host_return", }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(2); }); it("reads a bounded documented UXP timeline snapshot without falling back to CEP or mutating Premiere", async () => { const value = advancedHost(); const allSnapshot = await value.registry.dispatch("timeline.structure.inspect", { expectedSequenceId: "sequence-1", mediaType: "all", includeEmptyTracks: true, maxItems: 2, }); expect(allSnapshot).toMatchObject({ sequence: { id: "sequence-1", name: "Assembly" }, mediaType: "all", trackCounts: { video: 1, audio: 1 }, trackCount: 2, itemCount: 2, emptyTracksOmitted: 0, verificationBoundary: "bounded_track_item_readback", tracks: [ { mediaType: "video", trackIndex: 0, itemCount: 1, items: [{ name: "Interview V", startSeconds: 10, endSeconds: 20, speed: 1, reversed: false, disabled: false }] }, { mediaType: "audio", trackIndex: 0, itemCount: 1, items: [{ name: "Interview V", startSeconds: 10, endSeconds: 20, speed: 1, reversed: false, disabled: false }] }, ], }); expect(allSnapshot.trackCounts).toEqual({ video: 1, audio: 1 }); expect(value.trackItem.getProjectItem).not.toHaveBeenCalled(); expect(value.project.lockedAccess).not.toHaveBeenCalled(); expect(value.project.executeTransaction).not.toHaveBeenCalled(); const sourceOptIn = advancedHost(); const sourceSnapshot = await sourceOptIn.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, }); expect(sourceSnapshot.tracks).toMatchObject([ { mediaType: "video", trackIndex: 0, items: [{ sourceProjectItemId: "clip-1" }] }, ]); expect(sourceOptIn.trackItem.getProjectItem).toHaveBeenCalledTimes(1); expect(sourceOptIn.clip.getId).toHaveBeenCalledTimes(1); expect(sourceOptIn.clip.isSequence).not.toHaveBeenCalled(); expect(sourceOptIn.clip.isMergedClip).not.toHaveBeenCalled(); expect(sourceOptIn.clip.isMulticamClip).not.toHaveBeenCalled(); expect(sourceOptIn.clip.isOffline).not.toHaveBeenCalled(); expect(sourceOptIn.clip.getSequence).not.toHaveBeenCalled(); expect(sourceOptIn.project.lockedAccess).not.toHaveBeenCalled(); expect(sourceOptIn.project.executeTransaction).not.toHaveBeenCalled(); const classifiedSource = advancedHost(); classifiedSource.clip.isSequence?.mockResolvedValueOnce(true); classifiedSource.clip.isMergedClip?.mockResolvedValueOnce(true); classifiedSource.clip.isMulticamClip?.mockResolvedValueOnce(false); classifiedSource.clip.isOffline?.mockRejectedValueOnce(new Error("offline status unavailable")); await expect(classifiedSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true, })).resolves.toMatchObject({ tracks: [{ mediaType: "video", trackIndex: 0, items: [{ sourceProjectItemId: "clip-1", sourceProjectItemClassification: { isSequence: true, isMergedClip: true, isMulticamClip: false, isOffline: null, }, }] }], }); expect(classifiedSource.trackItem.getProjectItem).toHaveBeenCalledTimes(1); expect(classifiedSource.clip.getId).toHaveBeenCalledTimes(1); expect(classifiedSource.ppro.ClipProjectItem.cast).toHaveBeenCalledWith(classifiedSource.clip); expect(classifiedSource.clip.isSequence).toHaveBeenCalledTimes(1); expect(classifiedSource.clip.isMergedClip).toHaveBeenCalledTimes(1); expect(classifiedSource.clip.isMulticamClip).toHaveBeenCalledTimes(1); expect(classifiedSource.clip.isOffline).toHaveBeenCalledTimes(1); expect(classifiedSource.clip.getSequence).not.toHaveBeenCalled(); expect(classifiedSource.project.lockedAccess).not.toHaveBeenCalled(); expect(classifiedSource.project.executeTransaction).not.toHaveBeenCalled(); const contentTypeSource = advancedHost(); await expect(contentTypeSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true, })).resolves.toMatchObject({ tracks: [{ mediaType: "video", trackIndex: 0, items: [{ sourceProjectItemId: "clip-1", sourceProjectItemContentType: "media", }] }], }); expect(contentTypeSource.trackItem.getProjectItem).toHaveBeenCalledTimes(1); expect(contentTypeSource.ppro.ClipProjectItem.cast).toHaveBeenCalledWith(contentTypeSource.clip); expect(contentTypeSource.clip.getContentType).toHaveBeenCalledTimes(1); expect(contentTypeSource.clip.isSequence).not.toHaveBeenCalled(); expect(contentTypeSource.clip.isMergedClip).not.toHaveBeenCalled(); expect(contentTypeSource.clip.isMulticamClip).not.toHaveBeenCalled(); expect(contentTypeSource.clip.isOffline).not.toHaveBeenCalled(); expect(contentTypeSource.clip.getSequence).not.toHaveBeenCalled(); expect(contentTypeSource.project.lockedAccess).not.toHaveBeenCalled(); expect(contentTypeSource.project.executeTransaction).not.toHaveBeenCalled(); const unknownContentType = advancedHost(); unknownContentType.clip.getContentType?.mockResolvedValueOnce(99); await expect(unknownContentType.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemContentType: null }] }] }); const unavailableContentType = advancedHost(); unavailableContentType.clip.getContentType?.mockRejectedValueOnce(new Error("content type unavailable")); await expect(unavailableContentType.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemContentType: null }] }] }); const missingContentTypeEnum = advancedHost(); missingContentTypeEnum.ppro.Constants.ContentType = undefined; await expect(missingContentTypeEnum.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemContentType: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemContentType: null }] }] }); expect(missingContentTypeEnum.clip.getContentType).not.toHaveBeenCalled(); const nestedSource = advancedHost(); nestedSource.clip.isSequence?.mockResolvedValueOnce(true); await expect(nestedSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true, includeSourceNestedSequenceIdentity: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemId: "clip-1", sourceNestedSequenceId: "sequence-1", sourceProjectItemClassification: { isSequence: true }, }] }] }); expect(nestedSource.clip.getSequence).toHaveBeenCalledTimes(1); expect(nestedSource.project.lockedAccess).not.toHaveBeenCalled(); expect(nestedSource.project.executeTransaction).not.toHaveBeenCalled(); const nonSequenceNestedSource = advancedHost(); await expect(nonSequenceNestedSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true, includeSourceNestedSequenceIdentity: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceNestedSequenceId: null, sourceProjectItemClassification: { isSequence: false }, }] }] }); expect(nonSequenceNestedSource.clip.getSequence).not.toHaveBeenCalled(); const unavailableNestedSource = advancedHost(); unavailableNestedSource.clip.isSequence?.mockResolvedValueOnce(true); unavailableNestedSource.clip.getSequence?.mockRejectedValueOnce(new Error("nested sequence unavailable")); await expect(unavailableNestedSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true, includeSourceNestedSequenceIdentity: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceNestedSequenceId: null }] }] }); expect(unavailableNestedSource.clip.getSequence).toHaveBeenCalledTimes(1); const malformedNestedSource = advancedHost(); malformedNestedSource.clip.isSequence?.mockResolvedValueOnce(true); malformedNestedSource.clip.getSequence?.mockResolvedValueOnce({ guid: {} }); await expect(malformedNestedSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true, includeSourceNestedSequenceIdentity: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceNestedSequenceId: null }] }] }); const unavailableSource = advancedHost(); unavailableSource.trackItem.getProjectItem.mockRejectedValueOnce(new Error("source item unavailable")); await expect(unavailableSource.registry.dispatch("timeline.structure.inspect", { mediaType: "video", includeSourceProjectItems: true, includeSourceProjectItemClassification: true, })).resolves.toMatchObject({ tracks: [{ items: [{ sourceProjectItemId: null, sourceProjectItemClassification: null, }] }] }); expect(unavailableSource.ppro.ClipProjectItem.cast).not.toHaveBeenCalled(); const videoOnly = advancedHost(); const videoOnlySnapshot = await videoOnly.registry.dispatch("timeline.structure.inspect", { mediaType: "video" }); expect(videoOnlySnapshot.trackCounts).toEqual({ video: 1 }); expect(videoOnly.sequence.getAudioTrackCount).not.toHaveBeenCalled(); const audioOnly = advancedHost(); const audioOnlySnapshot = await audioOnly.registry.dispatch("timeline.structure.inspect", { mediaType: "audio" }); expect(audioOnlySnapshot.trackCounts).toEqual({ audio: 1 }); expect(audioOnly.sequence.getVideoTrackCount).not.toHaveBeenCalled(); await expect(value.registry.dispatch("timeline.structure.inspect", { expectedSequenceId: "another-sequence", })).rejects.toMatchObject({ code: "UXP_STALE_SEQUENCE" }); await expect(value.registry.dispatch("timeline.structure.inspect", { trackIndices: [0, 0], })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("timeline.structure.inspect", { includeSourceProjectItems: "yes", })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("timeline.structure.inspect", { includeSourceProjectItemClassification: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("timeline.structure.inspect", { includeSourceProjectItemContentType: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("timeline.structure.inspect", { includeSourceNestedSequenceIdentity: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("timeline.structure.inspect", { includeSourceProjectItems: true, includeSourceNestedSequenceIdentity: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("timeline.structure.inspect", { mediaType: "all", maxItems: 1, })).rejects.toMatchObject({ code: "UXP_TIMELINE_TOO_LARGE" }); expect(value.project.lockedAccess).not.toHaveBeenCalled(); }); it("fails closed before reading a broad unfiltered timeline and advertises the probe boundary", async () => { const large = advancedHost(); large.sequence.getVideoTrackCount.mockResolvedValue(65); await expect(large.registry.dispatch("timeline.structure.inspect", { mediaType: "video" })) .rejects.toMatchObject({ code: "UXP_TIMELINE_TOO_LARGE" }); expect(large.sequence.getVideoTrack).not.toHaveBeenCalled(); const combined = advancedHost(); combined.sequence.getVideoTrackCount.mockResolvedValue(40); combined.sequence.getAudioTrackCount.mockResolvedValue(25); await expect(combined.registry.dispatch("timeline.structure.inspect", { mediaType: "all" })) .rejects.toMatchObject({ code: "UXP_TIMELINE_TOO_LARGE" }); expect(combined.sequence.getVideoTrack).not.toHaveBeenCalled(); expect(combined.sequence.getAudioTrack).not.toHaveBeenCalled(); const outOfRange = advancedHost(); outOfRange.sequence.getVideoTrackCount.mockResolvedValue(1); await expect(outOfRange.registry.dispatch("timeline.structure.inspect", { mediaType: "video", trackIndices: [1], })).rejects.toMatchObject({ code: "UXP_TARGET_NOT_FOUND" }); expect(outOfRange.sequence.getVideoTrack).not.toHaveBeenCalled(); const unordered = advancedHost(); unordered.sequence.getVideoTrackCount.mockResolvedValue(2); const orderedSnapshot = await unordered.registry.dispatch("timeline.structure.inspect", { mediaType: "video", trackIndices: [1, 0], }); expect(orderedSnapshot.tracks.map((track: { trackIndex: number }) => track.trackIndex)).toEqual([0, 1]); expect(unordered.sequence.getVideoTrack.mock.calls.map(([trackIndex]: [number]) => trackIndex)).toEqual([0, 1]); const unavailable = advancedHost(); unavailable.ppro.Constants.TrackItemType = undefined; const capabilities = await unavailable.registry.capabilities(); expect(capabilities.commands["timeline.structure.inspect"]).toMatchObject({ supported: false, readOnly: true }); }); it("creates an atomic L-cut with synchronized timeline and source edges", async () => { const value = advancedHost(); await expect(value.registry.dispatch("trackItem.splitEdit", { kind: "l_cut", audioTrackIndex: 0, audioClipIndex: 0, videoTrackIndex: 0, videoClipIndex: 0, extensionSeconds: 2, operationId: "l-cut", })).resolves.toMatchObject({ splitEdit: "l_cut", extensionSeconds: 2, outcome: "verified", before: { audio: { endSeconds: 20, outSeconds: 10 }, video: { endSeconds: 20 } }, after: { endSeconds: 22, outSeconds: 12 }, }); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); }); it("uses complete keyframe preflight/readback and reports absent removals as no-ops", async () => { const value = advancedHost(); value.parameterState.keyframes = [1, 2, 3]; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0 }; await expect(value.registry.dispatch("parameters.keyframeRemove", { ...target, timeSeconds: 2 })) .resolves.toMatchObject({ removed: true, removalRequested: true, outcome: "verified" }); await expect(value.registry.dispatch("parameters.keyframeRemove", { ...target, timeSeconds: 2 })) .resolves.toMatchObject({ removed: false, unchanged: true, outcome: "verified", operation: { mutatesProject: false } }); value.parameterState.keyframes = [1, 2, 3]; await expect(value.registry.dispatch("parameters.keyframeRemoveRange", { ...target, timeSeconds: 1.5, endSeconds: 2.5 })) .resolves.toMatchObject({ removed: true, removalRequested: true, outcome: "verified" }); await expect(value.registry.dispatch("parameters.keyframeRemoveRange", { ...target, timeSeconds: 1.5, endSeconds: 2.5 })) .resolves.toMatchObject({ removed: false, unchanged: true, outcome: "verified" }); value.parameterState.keyframes = Array.from({ length: 257 }, (_, index) => index); await expect(value.registry.dispatch("parameters.keyframeRemove", { ...target, timeSeconds: 1 })) .rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); }); it("serializes guarded parameter animation-mode updates and verifies the native readback", async () => { const value = advancedHost(); const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedSequenceId: "sequence-1" }; let enterSnapshot: () => void = function () {}; let releaseSnapshot: () => void = function () {}; const enteredSnapshot = new Promise((resolve) => { enterSnapshot = resolve; }); const resumeSnapshot = new Promise((resolve) => { releaseSnapshot = resolve; }); value.parameter.areKeyframesSupported.mockImplementationOnce(async () => { enterSnapshot(); await resumeSnapshot; return true; }); const enable = value.registry.dispatch("parameters.timeVarying.set", { ...target, expectedTimeVarying: false, expectedKeyframeTimesSeconds: [], timeVarying: true, operationId: "enable-animation", }); await enteredSnapshot; const disable = value.registry.dispatch("parameters.timeVarying.set", { ...target, expectedTimeVarying: true, expectedKeyframeTimesSeconds: [], timeVarying: false, confirmDisableTimeVarying: true, operationId: "disable-animation", }); releaseSnapshot(); await expect(enable).resolves.toMatchObject({ updated: true, requestedTimeVarying: true, outcome: "verified", after: { timeVarying: true } }); await expect(disable).resolves.toMatchObject({ updated: true, requestedTimeVarying: false, outcome: "verified", after: { timeVarying: false } }); expect(value.parameter.createSetTimeVaryingAction).toHaveBeenNthCalledWith(1, true); expect(value.parameter.createSetTimeVaryingAction).toHaveBeenNthCalledWith(2, false); expect(value.project.executeTransaction).toHaveBeenCalledTimes(2); }); it("fails closed on incomplete or stale animation-mode preconditions before it creates an action", async () => { const value = advancedHost(); value.parameterState.varying = true; value.parameterState.keyframes = [1, 2]; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedSequenceId: "sequence-1", expectedTimeVarying: true, timeVarying: false }; await expect(value.registry.dispatch("parameters.timeVarying.set", { ...target, expectedKeyframeTimesSeconds: [1], confirmDisableTimeVarying: true, operationId: "stale-animation", })).rejects.toMatchObject({ code: "UXP_STALE_PARAMETER" }); await expect(value.registry.dispatch("parameters.timeVarying.set", { ...target, expectedKeyframeTimesSeconds: [1, 2], operationId: "unconfirmed-animation", })).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); expect(value.parameter.createSetTimeVaryingAction).not.toHaveBeenCalled(); await expect(value.registry.dispatch("parameters.timeVarying.set", { ...target, expectedKeyframeTimesSeconds: [1, 2], confirmDisableTimeVarying: true, operationId: "confirmed-animation", })).resolves.toMatchObject({ updated: true, after: { timeVarying: false, keyframeTimesSeconds: [1, 2] }, outcome: "verified" }); }); it("replays a completed guarded animation-mode operation without creating another action", async () => { const value = advancedHost(); const input = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedSequenceId: "sequence-1", expectedTimeVarying: false, expectedKeyframeTimesSeconds: [], timeVarying: true, operationId: "replay-animation-mode", }; const first = await value.registry.dispatch("parameters.timeVarying.set", input); expect(first).toMatchObject({ updated: true, after: { timeVarying: true } }); expect(first).not.toHaveProperty("replayed"); await expect(value.registry.dispatch("parameters.timeVarying.set", input)).resolves.toMatchObject({ updated: true, after: { timeVarying: true }, replayed: true, }); expect(value.parameter.createSetTimeVaryingAction).toHaveBeenCalledTimes(1); expect(value.project.executeTransaction).toHaveBeenCalledTimes(1); }); it("locates one exact, directional, or bounded-nearest keyframe with native position and interpolation readback", async () => { const value = advancedHost(); value.parameterState.keyframes = [1, 3, 7]; const target = { mediaType: "video", trackIndex: 0, clipIndex: 0, componentIndex: 0, paramIndex: 0, expectedComponentId: "ADBE Opacity", expectedParamName: "Opacity" }; await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "at", timeSeconds: 3 })).resolves.toMatchObject({ sequenceId: "sequence-1", direction: "at", referenceSeconds: 3, found: true, keyframe: { positionSeconds: 3, temporalInterpolationMode: 1 }, }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "next", timeSeconds: 3 })).resolves.toMatchObject({ direction: "next", found: true, keyframe: { positionSeconds: 7, temporalInterpolationMode: 1 }, }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "previous", timeSeconds: 3 })).resolves.toMatchObject({ direction: "previous", found: true, keyframe: { positionSeconds: 1, temporalInterpolationMode: 1 }, }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "next", timeSeconds: 7 })).resolves.toMatchObject({ direction: "next", found: false, keyframe: null, }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "nearest", timeSeconds: 2, endSeconds: 5 })).resolves.toMatchObject({ direction: "nearest", referenceSeconds: 2, rangeEndSeconds: 5, found: true, keyframe: { positionSeconds: 3, temporalInterpolationMode: 1 }, }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "nearest", timeSeconds: 3 })) .rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "nearest", timeSeconds: 5, endSeconds: 2 })) .rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); await expect(value.registry.dispatch("parameters.keyframe.inspect", { ...target, direction: "next", timeSeconds: 3, endSeconds: 5 })) .rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); expect(value.parameter.getKeyframePtr).toHaveBeenCalledWith({ seconds: 3 }); expect(value.parameter.findNextKeyframe).toHaveBeenCalledWith({ seconds: 3 }); expect(value.parameter.findPreviousKeyframe).toHaveBeenCalledWith({ seconds: 3 }); expect(value.parameter.findNearestKeyframe).toHaveBeenCalledWith({ seconds: 2 }, { seconds: 5 }); const staleTarget = advancedHost(); staleTarget.parameterState.keyframes = [3]; await expect(staleTarget.registry.dispatch("parameters.keyframe.inspect", { ...target, expectedParamName: "Stale Opacity", direction: "at", timeSeconds: 3, })).rejects.toMatchObject({ code: "UXP_STALE_PARAMETER" }); expect(staleTarget.parameter.getKeyframePtr).not.toHaveBeenCalled(); }); it("keeps direct sequence actions unverified with stable result keys and probes host methods", async () => { const value = advancedHost(); for (const [command, resultField] of [ ["sequences.activate", "activated"], ["sequences.open", "opened"], ["sequences.close", "closed"], ]) { await expect(value.registry.dispatch(command, { sequenceId: "sequence-1" })) .resolves.toMatchObject({ [resultField]: true, outcome: "committed_unverified", verified: false, verificationBoundary: "host_return" }); } await expect(value.registry.dispatch("sequences.open", { sequenceId: "sequence-1" })).resolves.not.toHaveProperty("opend"); await expect(value.registry.dispatch("sequenceSettings.update", { updates: { videoFrameRate: 241 }, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); delete (value.project as unknown as Record).importAEComps; await expect(value.registry.capabilities()).resolves.toMatchObject({ commands: { "project.import": { supported: false } }, }); const missingClose = advancedHost(); Reflect.deleteProperty(missingClose.project, "closeSequence"); await expect(missingClose.registry.capabilities()).resolves.toMatchObject({ commands: { "sequences.activate": { supported: true }, "sequences.open": { supported: true }, "sequences.close": { supported: false }, }, }); await expect(value.registry.dispatch("sequences.createFromMedia", { name: "New Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, })).resolves.toMatchObject({ created: true, outcome: "committed_unverified", verified: false, verificationBoundary: "create_sequence_host_return", }); await expect(value.registry.dispatch("sequences.subsequence", { sequenceId: "sequence-1", confirmNonUndoable: true, })).resolves.toMatchObject({ created: true, outcome: "committed_unverified", verified: false, verificationBoundary: "create_subsequence_host_return", }); }); it("creates an empty sequence only after confirmation, then verifies its identity and replays its receipt", async () => { const value = advancedHost(); await expect(value.registry.dispatch("sequences.createEmpty", { name: "Empty Assembly" })) .rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); expect(value.project.createSequence).not.toHaveBeenCalled(); await expect(value.registry.dispatch("sequences.createEmpty", { name: "Empty Assembly", confirmNonUndoable: true, })).rejects.toMatchObject({ code: "UXP_INVALID_ARGUMENT" }); expect(value.project.createSequence).not.toHaveBeenCalled(); await expect(value.registry.dispatch("sequences.createEmpty", { name: "Empty Assembly", confirmNonUndoable: true, operationId: "empty-sequence-1", })).resolves.toMatchObject({ created: true, partial: false, outcome: "verified", verified: true, verificationBoundary: "create_empty_sequence_identity_readback", sequence: { id: "sequence-empty-1", name: "Empty Assembly" }, operation: { mutatesProject: true, verification: { status: "verified", boundary: "create_empty_sequence_identity_readback" }, undo: { supported: false }, cancellation: { supported: false }, }, }); await expect(value.registry.dispatch("sequences.createEmpty", { name: "Empty Assembly", confirmNonUndoable: true, operationId: "empty-sequence-1", })).resolves.toMatchObject({ replayed: true, sequence: { id: "sequence-empty-1" } }); expect(value.project.createSequence).toHaveBeenCalledTimes(1); }); it("returns an unverified cached receipt if empty creation changes the project before rejecting", async () => { const value = advancedHost(); value.project.createSequence.mockImplementationOnce(async (name: string) => { value.sequences.push({ guid: "sequence-empty-after-error", name }); throw new Error("host rejected after creation"); }); const args = { name: "Recovered Empty", confirmNonUndoable: true, operationId: "empty-sequence-error" }; await expect(value.registry.dispatch("sequences.createEmpty", args)).resolves.toMatchObject({ created: true, partial: true, outcome: "committed_unverified", verified: false, verificationBoundary: "create_empty_sequence_host_reconciliation", sequence: { id: "sequence-empty-after-error", name: "Recovered Empty" }, operation: { cancellation: { supported: false } }, }); await expect(value.registry.dispatch("sequences.createEmpty", args)).resolves.toMatchObject({ replayed: true, partial: true }); expect(value.project.createSequence).toHaveBeenCalledTimes(1); }); it("caches partial receipts when direct sequence creation mutates before rejecting or loses identity", async () => { const rejected = advancedHost(); rejected.project.createSequenceFromMedia.mockImplementationOnce(async (name: string) => { rejected.sequences.push({ guid: "sequence-created-before-error", name }); throw new Error("host rejected after creation"); }); await expect(rejected.registry.dispatch("sequences.createFromMedia", { name: "Recovered Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, operationId: "sequence-host-error", })).resolves.toMatchObject({ created: true, partial: true, verificationBoundary: "create_sequence_host_reconciliation", sequence: { id: "sequence-created-before-error", name: "Recovered Assembly" }, }); await expect(rejected.registry.dispatch("sequences.createFromMedia", { name: "Recovered Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, operationId: "sequence-host-error", })).resolves.toMatchObject({ partial: true, replayed: true }); const missingIdentity = advancedHost(); missingIdentity.project.createSequenceFromMedia.mockResolvedValueOnce({ name: "Unknown identity" }); await expect(missingIdentity.registry.dispatch("sequences.createFromMedia", { name: "Unknown identity", projectItemIds: ["clip-1"], confirmNonUndoable: true, })).resolves.toMatchObject({ created: true, partial: true, verificationBoundary: "create_sequence_identity_readback", sequence: { id: "" }, }); }); it("reconciles a subsequence that exists when the direct host call rejects", async () => { const value = advancedHost(); value.sequence.createSubsequence.mockImplementationOnce(async () => { value.sequences.push({ guid: "subsequence-created-before-error", name: "Recovered Subsequence" }); throw new Error("host rejected after creation"); }); await expect(value.registry.dispatch("sequences.subsequence", { sequenceId: "sequence-1", confirmNonUndoable: true, operationId: "subsequence-host-error", })).resolves.toMatchObject({ created: true, partial: true, verificationBoundary: "create_subsequence_host_reconciliation", sequence: { id: "subsequence-created-before-error", name: "Recovered Subsequence" }, }); }); it("surfaces direct sequence rejections when no created sequence can be reconciled", async () => { const sequenceCreation = advancedHost(); sequenceCreation.project.createSequenceFromMedia.mockRejectedValueOnce(new Error("host rejected")); await expect(sequenceCreation.registry.dispatch("sequences.createFromMedia", { name: "Rejected Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, })).rejects.toMatchObject({ code: "UXP_HOST_REJECTED" }); const subsequenceCreation = advancedHost(); subsequenceCreation.sequence.createSubsequence.mockRejectedValueOnce(new Error("host rejected")); await expect(subsequenceCreation.registry.dispatch("sequences.subsequence", { sequenceId: "sequence-1", confirmNonUndoable: true, })).rejects.toMatchObject({ code: "UXP_HOST_REJECTED" }); }); it("caches partial direct receipts when post-rejection reconciliation cannot be read", async () => { const sequenceCreation = advancedHost(); let sequenceReads = 0; sequenceCreation.project.getSequences.mockImplementation(async () => { sequenceReads++; if (sequenceReads > 1) throw new Error("sequence readback unavailable"); return sequenceCreation.sequences; }); sequenceCreation.project.createSequenceFromMedia.mockRejectedValueOnce(new Error("host rejected")); const sequenceArgs = { name: "Unverified Assembly", projectItemIds: ["clip-1"], confirmNonUndoable: true, operationId: "sequence-readback-failed", }; await expect(sequenceCreation.registry.dispatch("sequences.createFromMedia", sequenceArgs)).resolves.toMatchObject({ created: false, partial: true, verificationBoundary: "create_sequence_reconciliation_readback_failed", }); await expect(sequenceCreation.registry.dispatch("sequences.createFromMedia", sequenceArgs)).resolves.toMatchObject({ partial: true, replayed: true }); const subsequenceCreation = advancedHost(); let subsequenceReads = 0; subsequenceCreation.project.getSequences.mockImplementation(async () => { subsequenceReads++; if (subsequenceReads > 2) throw new Error("sequence readback unavailable"); return subsequenceCreation.sequences; }); subsequenceCreation.sequence.createSubsequence.mockRejectedValueOnce(new Error("host rejected")); const subsequenceArgs = { sequenceId: "sequence-1", confirmNonUndoable: true, operationId: "subsequence-readback-failed", }; await expect(subsequenceCreation.registry.dispatch("sequences.subsequence", subsequenceArgs)).resolves.toMatchObject({ created: false, partial: true, verificationBoundary: "create_subsequence_reconciliation_readback_failed", }); await expect(subsequenceCreation.registry.dispatch("sequences.subsequence", subsequenceArgs)).resolves.toMatchObject({ partial: true, replayed: true }); }); it("bounds ID-targeted sequence lookup before invoking the host mutation", async () => { const value = advancedHost(); value.project.getSequences.mockResolvedValue(Array.from({ length: 1025 }, (_, index) => ({ guid: `sequence-${index}`, name: `Sequence ${index}`, }))); await expect(value.registry.dispatch("sequences.close", { sequenceId: "sequence-1024" })) .rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(value.project.closeSequence).not.toHaveBeenCalled(); await expect(value.registry.dispatch("sequences.delete", { confirmNonUndoable: true })) .rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(value.project.deleteSequence).not.toHaveBeenCalled(); }); it("rejects bounded collection additions before starting a host mutation", async () => { const markerValue = advancedHost(); markerValue.markers.getMarkers.mockReturnValue(Array.from({ length: 2048 }, () => markerValue.markerValues[0])); await expect(markerValue.registry.dispatch("markers.add", { name: "Over capacity", operationId: "marker-capacity", })).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(markerValue.markers.createAddMarkerAction).not.toHaveBeenCalled(); expect(markerValue.project.lockedAccess).not.toHaveBeenCalled(); const binValue = advancedHost(); binValue.bin.children = Array.from({ length: 1024 }, () => binValue.clip); await expect(binValue.registry.dispatch("bins.create", { parentBinId: "bin-1", name: "Over capacity", operationId: "bin-capacity", })).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(binValue.bin.createBinAction).not.toHaveBeenCalled(); expect(binValue.project.lockedAccess).not.toHaveBeenCalled(); const smartBinValue = advancedHost(); smartBinValue.bin.children = Array.from({ length: 1024 }, () => smartBinValue.clip); await expect(smartBinValue.registry.dispatch("bins.createSmart", { parentBinId: "bin-1", name: "Over capacity", searchQuery: "label:red", operationId: "smart-bin-capacity", })).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(smartBinValue.bin.createSmartBinAction).not.toHaveBeenCalled(); expect(smartBinValue.project.lockedAccess).not.toHaveBeenCalled(); const sequenceValue = advancedHost(); sequenceValue.project.getSequences.mockResolvedValue(Array.from({ length: 1024 }, (_, index) => ({ guid: `sequence-${index + 1}`, name: `Sequence ${index + 1}`, }))); await expect(sequenceValue.registry.dispatch("sequences.clone", { operationId: "sequence-capacity", })).rejects.toMatchObject({ code: "UXP_PROJECT_TOO_LARGE" }); expect(sequenceValue.sequence.createCloneAction).not.toHaveBeenCalled(); expect(sequenceValue.project.lockedAccess).not.toHaveBeenCalled(); }); it("serializes distinct append operations against each target capacity", async () => { const markerValue = advancedHost(); markerValue.markerValues.push(...Array.from({ length: 2046 }, () => markerValue.markerValues[0])); const markerResults = await Promise.allSettled([ markerValue.registry.dispatch("markers.add", { name: "First", operationId: "marker-first" }), markerValue.registry.dispatch("markers.add", { name: "Second", operationId: "marker-second" }), ]); expect(markerResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]); expect(markerResults.find((result) => result.status === "rejected")).toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } }); expect(markerValue.markers.createAddMarkerAction).toHaveBeenCalledOnce(); expect(markerValue.markerValues).toHaveLength(2048); const binValue = advancedHost(); binValue.bin.children = Array.from({ length: 1023 }, () => binValue.clip); const binResults = await Promise.allSettled([ binValue.registry.dispatch("bins.create", { parentBinId: "bin-1", name: "Regular", operationId: "bin-first", }), binValue.registry.dispatch("bins.createSmart", { parentBinId: "bin-1", name: "Smart", searchQuery: "label:red", operationId: "bin-second", }), ]); expect(binResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]); expect(binResults.find((result) => result.status === "rejected")).toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } }); expect((binValue.bin.createBinAction?.mock.calls.length || 0) + (binValue.bin.createSmartBinAction?.mock.calls.length || 0)).toBe(1); expect(binValue.bin.children).toHaveLength(1024); const sequenceValue = advancedHost(); sequenceValue.sequences.push(...Array.from({ length: 1022 }, (_, index) => ({ guid: `existing-sequence-${index}`, name: `Existing Sequence ${index}`, }))); const sequenceResults = await Promise.allSettled([ sequenceValue.registry.dispatch("sequences.clone", { operationId: "sequence-first" }), sequenceValue.registry.dispatch("sequences.clone", { operationId: "sequence-second" }), ]); expect(sequenceResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]); expect(sequenceResults.find((result) => result.status === "rejected")).toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } }); expect(sequenceValue.sequence.createCloneAction).toHaveBeenCalledOnce(); expect(sequenceValue.sequences).toHaveLength(1024); const emptySequenceValue = advancedHost(); emptySequenceValue.sequences.push(...Array.from({ length: 1022 }, (_, index) => ({ guid: `existing-empty-sequence-${index}`, name: `Existing Empty Sequence ${index}`, }))); const emptyResults = await Promise.allSettled([ emptySequenceValue.registry.dispatch("sequences.createEmpty", { name: "First Empty", confirmNonUndoable: true, operationId: "empty-first", }), emptySequenceValue.registry.dispatch("sequences.createEmpty", { name: "Second Empty", confirmNonUndoable: true, operationId: "empty-second", }), ]); expect(emptyResults.map((result) => result.status).sort()).toEqual(["fulfilled", "rejected"]); expect(emptyResults.find((result) => result.status === "rejected")) .toMatchObject({ reason: { code: "UXP_PROJECT_TOO_LARGE" } }); expect(emptySequenceValue.project.createSequence).toHaveBeenCalledOnce(); expect(emptySequenceValue.sequences).toHaveLength(1024); }); it("clones sequences with identity readback and gates AME writes on explicit confirmation", async () => { const value = advancedHost(); await expect(value.registry.dispatch("sequences.clone", { sequenceId: "sequence-1", operationId: "sequence-clone", })).resolves.toMatchObject({ cloned: true, outcome: "verified", source: { id: "sequence-1" }, sequence: { id: "sequence-2", name: "Assembly Copy" }, }); await expect(value.registry.dispatch("encoder.sequence", { sequenceId: "sequence-1", exportType: "queueToAme", outputFile: "D:/Approved/output.mp4", presetFile: "D:/Approved/h264.epr", })).rejects.toMatchObject({ code: "UXP_CONFIRMATION_REQUIRED" }); await expect(value.registry.dispatch("encoder.sequence", { sequenceId: "sequence-1", exportType: "queueToAme", outputFile: "D:/Approved/output.mp4", presetFile: "D:/Approved/h264.epr", confirmExternalWrite: true, operationId: "encode-sequence", })).resolves.toMatchObject({ queued: true, kind: "sequence", outcome: "committed_unverified", verified: false, verificationBoundary: "encoder_host_return", encodeJob: { jobId: "encode-sequence", state: "accepted", terminal: false }, }); expect(value.manager.exportSequence).toHaveBeenCalledWith( expect.objectContaining({ guid: "sequence-1" }), "ame", "D:/Approved/output.mp4", "D:/Approved/h264.epr", true, ); value.events.recordHostEvent({ category: "encoder", name: "encoder.queued" }); value.events.recordHostEvent({ category: "encoder", name: "encoder.complete" }); await expect(value.registry.dispatch("encoder.wait", { jobId: "encode-sequence", timeoutMs: 100, })).resolves.toMatchObject({ timedOut: false, job: { state: "completed", terminal: true, verificationBoundary: "encoder_terminal_event_only" }, }); const noProject = advancedHost(); noProject.ppro.Project.getActiveProject.mockResolvedValue(null); await expect(noProject.registry.dispatch("encoder.preflight", {})).resolves.toEqual({ ameInstalled: true, extension: null, sequenceId: null, }); }); });