feat: add audio slot feature to Composer UI (Closes #14)#30
feat: add audio slot feature to Composer UI (Closes #14)#30socialawy-dev wants to merge 49 commits into
Conversation
Phase 2 — Spec Schema Phase 3 — Composer UI Tasks
- [x] Replaced no-op globalAlpha reset with proper fade-from-black overlay
- Implemented adapter interfaces with exact shape definitions for Beat, MapResult, DirectorInput/Output, and LLMAdapter contract - Created native fetch-based Google Gemini and Ollama adapters with JSON response enforcement - Added stubs for Claude and OpenAI providers - Implemented parser with fail-fast validation for extracting temporal/thematic beats from scripts - Implemented mapper with hallucination bounds validation for assigning beats to images - Created orchestrator with self-healing retry mechanism and AJV JSON Schema validation - Added comprehensive test suite verifying retry pipeline (4 invocation sequences) - Built Director Panel UI with story script input, style selection, and provider configuration - Implemented loading states and review/accept flow with progressive status updates - All TypeScript compilation and ESLint validation pass with zero warnings - Full test suite passes: 76 tests across 7 test files
- Step 1: src/spec/schema.ts
### Step 6: Three spatial transitions src/engine/transitions/wipeLeft.ts — NEW src/engine/transitions/wipeDown.ts — NEW src/engine/transitions/slideLeft.ts — NEW
Step 9: src/director/prompts.ts — update transition guide
- `tsc` ✅ clean - `lint` ✅ 0 errors, 0 warnings - `build` ✅ 365 kB, 1.69s - `tests` ✅ 93/93 passed (7 files)
… gates pass: 108/108 tests (15 new persistence/migration tests + 93 existing) tsc, lint, build all clean New files: migrate.ts, persistence.ts, ProjectPicker.tsx, persistence.test.ts Modified: project.ts (auto-save + project CRUD), App.tsx (picker + load-on-mount), style.css, package.json The auto-save debounce is set to 2 seconds
- Update movePlate function to properly maintain selectedPlateIdx - When moving selected plate: update index to new position - When moving other plates: adjust selected index based on position shifts - Maintains visual selection consistency during drag operations - Add devlog entry documenting the fix Resolves issue where selection highlight didn't move during reordering
Adds the multi-agent development pipeline: - gemini-review.yml: Gemini 3.1 Pro reviews PRs, auto-merges APPROVE, escalates complex changes - jules-issues.yml: Jules picks unassigned issues daily, implements and creates PRs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.56.1 to 8.57.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.57.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ory (#4) Bumps the npm_and_yarn group with 1 update in the / directory: [undici](https://github.com/nodejs/undici). Updates `undici` from 7.22.0 to 7.24.1 - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](nodejs/undici@v7.22.0...v7.24.1) --- updated-dependencies: - dependency-name: undici dependency-version: 7.24.1 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [globals](https://github.com/sindresorhus/globals) from 17.3.0 to 17.4.0. - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](sindresorhus/globals@v17.3.0...v17.4.0) --- updated-dependencies: - dependency-name: globals dependency-version: 17.4.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) from 4.0.18 to 4.1.0. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.0/packages/ui) --- updated-dependencies: - dependency-name: "@vitest/ui" dependency-version: 4.1.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.56.1 to 8.57.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-version: 8.57.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [zustand](https://github.com/pmndrs/zustand) from 5.0.11 to 5.0.12. - [Release notes](https://github.com/pmndrs/zustand/releases) - [Commits](pmndrs/zustand@v5.0.11...v5.0.12) --- updated-dependencies: - dependency-name: zustand dependency-version: 5.0.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Prevents duplicate reviews when multiple PRs trigger simultaneously. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 8.0.0. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/create-vite@8.0.0/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 8.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [jsdom](https://github.com/jsdom/jsdom) from 28.1.0 to 29.0.0. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/v29.0.0/Changelog.md) - [Commits](jsdom/jsdom@v28.1.0...v29.0.0) --- updated-dependencies: - dependency-name: jsdom dependency-version: 29.0.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.3 to 10.0.3. - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](eslint/eslint@v9.39.3...v10.0.3) --- updated-dependencies: - dependency-name: eslint dependency-version: 10.0.3 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 5.1.4 to 6.0.1. - [Release notes](https://github.com/vitejs/vite-plugin-react/releases) - [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.1/packages/plugin-react) --- updated-dependencies: - dependency-name: "@vitejs/plugin-react" dependency-version: 6.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
- Add "Save" button to the composer ExportBar. - Auto-save current project to IndexedDB before WebM export. - Update tests to verify auto-save triggers when Export and Save are clicked. - Add @eslint/js dependency to resolve flat-config parsing errors. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: socialawy <24765060+socialawy@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) from 4.2.1 to 4.2.2. - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.2/packages/@tailwindcss-postcss) --- updated-dependencies: - dependency-name: "@tailwindcss/postcss" dependency-version: 4.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.57.0 to 8.57.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.57.1/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.57.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) from 4.2.1 to 4.2.2. - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.2/packages/tailwindcss) --- updated-dependencies: - dependency-name: tailwindcss dependency-version: 4.2.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Adds a single audio track reference to the sequence spec and Composer UI, allowing users to attach an audio file to their sequence. Included is a waveform visualization and track controls like volume and offset. - Added audio field to sequence.json schema. - Added AudioPanel to Composer UI with file picker, volume, offset and mute controls. - Implemented IndexedDB storage for audio files. - Added waveform visualization using Canvas2D and Web Audio API. - Re-used and extended IDB persistence and project store logic. - Included comprehensive test suite for AudioPanel. Co-authored-by: socialawy <24765060+socialawy@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
|
|
🚨 Escalation: This PR needs human review. @socialawy please check. |
There was a problem hiding this comment.
Code Review
This pull request introduces background audio support, including a new AudioPanel component for managing audio tracks, waveform visualization, and playback synchronization in the PreviewCanvas. The implementation also extends the project schema and persistence layer to handle audio files. Key feedback includes ensuring the AudioContext is properly closed in a finally block to prevent resource leaks, removing an unnecessary setTimeout in the AudioPanel state management, and adding bounds checks for the waveform calculation loop. Additionally, the audio synchronization logic should be tightened by reducing the drift threshold and handling potential NaN values for audio duration.
| const loadAudio = async () => { | ||
| try { | ||
| const response = await fetch(audioUrl); | ||
| const arrayBuffer = await response.arrayBuffer(); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)(); | ||
| const buffer = await audioCtx.decodeAudioData(arrayBuffer); | ||
| if (isMounted) setAudioBuffer(buffer); | ||
| audioCtx.close(); | ||
| } catch (err) { | ||
| console.error('[MotionPlate] Failed to decode audio for waveform:', err); | ||
| } | ||
| }; |
There was a problem hiding this comment.
The AudioContext is created but only closed if decodeAudioData succeeds. If an error occurs during fetch or decodeAudioData, the context remains open, which can lead to hitting the browser's hardware limit for concurrent audio contexts (often as low as 6 in some browsers). It should be closed in a finally block to ensure resources are always released.
| const loadAudio = async () => { | |
| try { | |
| const response = await fetch(audioUrl); | |
| const arrayBuffer = await response.arrayBuffer(); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)(); | |
| const buffer = await audioCtx.decodeAudioData(arrayBuffer); | |
| if (isMounted) setAudioBuffer(buffer); | |
| audioCtx.close(); | |
| } catch (err) { | |
| console.error('[MotionPlate] Failed to decode audio for waveform:', err); | |
| } | |
| }; | |
| const loadAudio = async () => { | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)(); | |
| try { | |
| const response = await fetch(audioUrl); | |
| const arrayBuffer = await response.arrayBuffer(); | |
| const buffer = await audioCtx.decodeAudioData(arrayBuffer); | |
| if (isMounted) setAudioBuffer(buffer); | |
| } catch (err) { | |
| console.error('[MotionPlate] Failed to decode audio for waveform:', err); | |
| } finally { | |
| audioCtx.close(); | |
| } | |
| }; |
| if (!audioUrl) { | ||
| // Safe to call synchronously if we know what we are doing, but | ||
| // avoiding cascading renders warning by putting it in a timeout | ||
| // or just accepting that the url isn't there and we clear it. | ||
| // Actually, we can just use setAudioBuffer inside the effect body but let's avoid the lint warning | ||
| setTimeout(() => { | ||
| if (isMounted) setAudioBuffer(null); | ||
| }, 0); | ||
| return; | ||
| } |
There was a problem hiding this comment.
The setTimeout wrapper for setAudioBuffer(null) is unnecessary. Calling state updates inside useEffect is standard practice and does not trigger the "cascading renders" warning in this context. Removing the timeout simplifies the code and ensures the state is cleared immediately when the URL is removed.
if (!audioUrl) {
setAudioBuffer(null);
return;
}
| for (let j = 0; j < step; j++) { | ||
| const datum = data[(i * step) + j]; | ||
| if (datum < min) min = datum; | ||
| if (datum > max) max = datum; | ||
| } |
There was a problem hiding this comment.
The inner loop for waveform calculation does not check if the index is within the bounds of the data array. When (i * step) + j exceeds data.length, data[index] returns undefined, which causes the min/max logic to fail (as comparisons with undefined return false) and results in incorrect drawing at the end of the waveform. Adding a bounds check ensures robustness.
for (let j = 0; j < step; j++) {
const idx = (i * step) + j;
if (idx >= data.length) break;
const datum = data[idx];
if (datum < min) min = datum;
if (datum > max) max = datum;
}
| audioEl.volume = spec.audio?.volume ?? 1.0; | ||
|
|
||
| if (isPlaying) { | ||
| if (targetTime >= 0 && targetTime <= audioEl.duration) { |
There was a problem hiding this comment.
The condition targetTime <= audioEl.duration may fail if the audio metadata hasn't loaded yet, as audioEl.duration will be NaN. It's safer to use a fallback or check for NaN to ensure consistent behavior, similar to how it's handled later in the same effect (line 81).
| if (targetTime >= 0 && targetTime <= audioEl.duration) { | |
| if (targetTime >= 0 && targetTime <= (audioEl.duration || 0)) { |
| if (targetTime >= 0 && targetTime <= audioEl.duration) { | ||
| // Play if we are within valid bounds | ||
| // Only seek if we are drifted (e.g. over 0.2s) | ||
| if (Math.abs(audioEl.currentTime - targetTime) > 0.2) { |
There was a problem hiding this comment.
A drift threshold of 0.2s (200ms) is quite high for audio synchronization. At 30fps, this represents a lag of 6 frames, which is clearly noticeable to users. Reducing this to 0.1s or lower would provide a much tighter and more professional sync experience.
| if (Math.abs(audioEl.currentTime - targetTime) > 0.2) { | |
| if (Math.abs(audioEl.currentTime - targetTime) > 0.1) { |
|
This PR has merge conflicts after #31 was merged. Needs a rebase to resolve conflicts before merging. |
I have successfully rebased the branch on top of |
Adds a single audio track reference to the sequence spec and Composer UI, allowing users to attach an audio file to their sequence. Included is a waveform visualization and track controls like volume and offset. - Added audio field to sequence.json schema. - Added AudioPanel to Composer UI with file picker, volume, offset and mute controls. - Implemented IndexedDB storage for audio files. - Added waveform visualization using Canvas2D and Web Audio API. - Re-used and extended IDB persistence and project store logic. - Included comprehensive test suite for AudioPanel. - Rebased branch on main to fix merge conflicts. - Addressed code review feedback to close audio context. Co-authored-by: socialawy <24765060+socialawy@users.noreply.github.com>
Adds an audio track feature to MotionPlate, enabling users to attach, control, and visualize an audio background clip for their motion sequence.
Summary of Changes
audiofield to theSequencespec (src/spec/schema.ts).AudioPanelcomponent (src/composer/AudioPanel.tsx) that includes a file picker, displays the selected file name, allows removing audio, implements a waveform visualization (via Web Audio API), and supports volume, offset, and mute adjustments. Added it to theApplayout.StoredProjectinsrc/store/persistence.tsto serialize/deserialize the audio file blob alongside the existing project spec and images.useProjectStore(src/store/project.ts) to manage audio state alongside sequences and history, and properly cleanup object URLs to prevent memory leaks.PreviewCanvasto render a hidden HTML<audio>element that syncs withusePlaybackStore'scurrentTimeandisPlayingstate.AudioContextis properly closed after decoding audio data to avoid hitting hardware limits.AudioPanel(tests/composer/AudioPanel.test.tsx), verifying state interactions, volume changes, and file upload functionality. Required installing@testing-library/reactand@testing-library/user-event.PR created automatically by Jules for task 14016837153224406116 started by @socialawy