- Replace RegExp constructor with replaceAll() for @variable substitution
in promptConstructor to prevent regex injection via variable names
- Wrap annotation, prompt, and promptConstructor execution cases in
try/catch to gracefully handle errors (log + set node error, continue)
- Fix MediaBunny resource cleanup type safety with closeable type guard
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AudioInputNode: use named event handlers with removeEventListener in cleanup
- deduplicatedFetch: track setTimeout IDs and clear them in clearFetchCache()
- waitForPendingImageSyncs: clear timeout in finally block after Promise.race
- decodeWithMediaBunny: wrap in try/finally to close sink, input, blobSource
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Offset segmentBaseTime by frameInterval so the first frame starts at
timestamp 0 instead of -frameInterval, which the encoder rejects.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Split AudioInputNode waveform effect to avoid ResizeObserver churn on every animation tick
- Add missing kieApiKey/wavespeedApiKey deps to ModelParameters useEffect
- Clean up VideoSampleSource/Output on error in useApplySpeedCurve
- Fix off-by-one in useStitchVideos duplicate-frame skip (<= to <) and add try/finally for resource cleanup
- Replace leftover currentNodeId: null with currentNodeIds: [] in workflowStore
- Store full response metadata (status, headers, body) in deduplicatedFetch cache
- Add in-flight guard to imageStorage to prevent duplicate concurrent saves
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cover 11 new source files with 280 specs across pure function libs,
React components, and async hook logic. Key areas: easing functions,
bezier presets, speed curve warping, video encoding config,
AudioInputNode, EaseCurveNode, CubicBezierEditor, audio mixing,
audio visualization, video stitching, and speed curve application.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- useApplySpeedCurve hook with MediaBunny encoding pipeline (no frame blending, no preview mode)
- Standalone applySpeedCurveAsync export for store/API usage
- SpeedCurveProgress type for progress tracking
- EaseCurveNodeData interface with bezierHandles, easingPreset, outputDuration, status fields
- "easeCurve" added to NodeType union and WorkflowNodeData
- Default dimensions (340x480) and node data (easeInOutSine preset) registered
- Shared applySpeedCurveCore extracts common logic between hook and standalone function
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After all video frames are processed (phase 1), the final video duration
is known from highestWrittenTimestamp. Audio is now trimmed to that
exact duration before being encoded into the MP4 (phase 2). Prevents
audio tracks from extending beyond the video.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When videoDuration is 0 (unknown at call time), prepareAudioAsync was
computing targetDuration as max(0.1, 0) = 0.1 seconds, truncating the
audio to 100ms. Now computes the actual decoded audio duration from
buffers and uses that when videoDuration <= 0.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use MediaBunny as the primary decoder following the documented pattern:
getPrimaryAudioTrack() + canDecode() check + AudioBufferSink.buffers().
Falls back to Web Audio API's decodeAudioData if MediaBunny cannot parse
the format (e.g. unusual codecs). This ensures MediaBunny handles
container formats it supports while Web Audio API catches the rest.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MediaBunny's Input/BlobSource is designed for video containers and cannot
parse standalone audio files (MP3, WAV, etc.), causing "unsupported format"
errors at getAudioTracks(). Replace with Web Audio API's decodeAudioData
which natively handles all browser-supported audio formats.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Port useAudioVisualization.ts with waveform peak extraction
- Port useAudioMixing.ts with prepareAudioAsync standalone function
- Port useStitchVideos.ts with stitchVideosAsync and checkEncoderSupport
- Export applyFades from useAudioMixing for shared use
- Remove preview mode logic from useStitchVideos (always full quality)
- Inline constants (DEFAULT_BITRATE, MAX_OUTPUT_FPS) in useStitchVideos
- All hooks use MediaBunny for audio/video processing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract autocomplete logic to usePromptAutocomplete hook
- Add PromptConstructorEditorModal with portal rendering
- Sync PromptConstructor outputText reactively for downstream nodes
- Add headerButtons prop to BaseNode for custom header actions
- Rename Blank Canvas to New Project with ProjectSetupModal integration
- Auto-push generated images to connected OutputGallery nodes
- Deduplicate images in OutputGallery during workflow execution
- Move PromptNode variable button to header with hover animation
- Extract AvailableVariable interface to types/nodes.ts
- Update tests for onNewProject rename
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Template Explorer Enhancements
- Add rich metadata and thumbnails for community workflows
- Create communityWorkflows.ts config for workflow metadata
- Reuse TemplateCard for community workflows with tag filtering
- Search now includes workflow descriptions
## Comment Navigation System
- Add comment navigation icon in header with unviewed count badge
- Navigate between nodes with comments using prev/next controls
- Tooltips show comment preview on hover
## Output Node Improvements
- Add custom filename and createDirectory options for output saves
- Remove default React Flow styling from output nodes
- Fix video output node connections
- Preserve node height on resize
## Image Handling Fixes
- Resolve image history race condition and duplicate images on save
- Preserve image history IDs when saving to new directory
- Verify outputImageRef matches selected history index on save
- Persist useExternalImageStorage setting in workflow config
- Paste images directly into selected imageInput nodes
## Model & Generation Fixes
- Use selectedModel.modelId for Gemini generation
- Return 200 with notFound flag instead of 404 for missing files
## Other
- Add git-lfs tracking for large workflow files
- Remove deprecated example workflows
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>