feat: Add timeline track and clip creation (Issue #10)#136
Open
pannous wants to merge 2 commits into
Open
Conversation
Implement the ability to create new tracks and clips directly in the timeline editor, addressing issue jbilcke-hf#10: 1. createTrack(category) - creates a new track with the specified segment category (VIDEO, IMAGE, DIALOGUE, etc.) 2. createClip({track, startTimeInMs, ...}) - creates a new clip/segment on a given track at the cursor position 3. moveSegmentToTrack(segment, targetTrack) - moves a segment between tracks with category compatibility validation and collision detection 4. TimelineToolbar UI component with category selector, track selector, and buttons for creating tracks and clips Resolves jbilcke-hf#10
There was a problem hiding this comment.
Pull request overview
Adds first-class timeline editing controls so users can create tracks and clips directly in the editor, supporting Issue #10’s “create content in timeline” workflow.
Changes:
- Extended the timeline store with
createTrack,createClip, andmoveSegmentToTrackmodifiers. - Implemented track/clip creation and segment-to-track move validation in the timeline Zustand store.
- Added a new
TimelineToolbarUI and integrated it above the timeline canvas in the app.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/timeline/src/types/timeline.ts | Extends TimelineStoreModifiers typings to include new track/clip APIs. |
| packages/timeline/src/hooks/useTimeline.ts | Implements track creation, clip creation via newSegment, and cross-track move validation. |
| packages/app/src/components/core/timeline/index.tsx | Wraps the timeline in a container and mounts the new toolbar above the canvas. |
| packages/app/src/components/core/timeline/TimelineToolbar.tsx | New toolbar UI for selecting category/track and creating tracks/clips. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| isPreview, | ||
| height: isPreview ? defaultPreviewHeight : defaultCellHeight, | ||
| hue: 0, | ||
| occupied: false, |
Comment on lines
+1276
to
+1306
| const trackInfo = tracks[track] | ||
| const trackCategory = category || ( | ||
| trackInfo | ||
| ? ClapSegmentCategory[trackInfo.name as keyof typeof ClapSegmentCategory] || ClapSegmentCategory.GENERIC | ||
| : ClapSegmentCategory.GENERIC | ||
| ) | ||
|
|
||
| const defaultDurationInMs = defaultSegmentDurationInSteps * durationInMsPerStep | ||
| const segmentEndTimeInMs = endTimeInMs || (startTimeInMs + defaultDurationInMs) | ||
|
|
||
| const outputType = | ||
| trackCategory === ClapSegmentCategory.VIDEO ? ClapOutputType.VIDEO | ||
| : trackCategory === ClapSegmentCategory.IMAGE ? ClapOutputType.IMAGE | ||
| : trackCategory === ClapSegmentCategory.DIALOGUE ? ClapOutputType.AUDIO | ||
| : trackCategory === ClapSegmentCategory.MUSIC ? ClapOutputType.AUDIO | ||
| : trackCategory === ClapSegmentCategory.SOUND ? ClapOutputType.AUDIO | ||
| : ClapOutputType.TEXT | ||
|
|
||
| const clapSegment = newSegment({ | ||
| track, | ||
| startTimeInMs, | ||
| endTimeInMs: segmentEndTimeInMs, | ||
| category: trackCategory, | ||
| prompt, | ||
| outputType, | ||
| }) | ||
|
|
||
| const segment = await clapSegmentToTimelineSegment(clapSegment) | ||
|
|
||
| await addSegment({ segment, startTimeInMs, track }) | ||
|
|
Comment on lines
+1342
to
+1359
| segment.track = targetTrack | ||
|
|
||
| // Update track info if it was empty | ||
| if (!targetTrackInfo.occupied) { | ||
| const isPreview = | ||
| segment.category === ClapSegmentCategory.IMAGE || | ||
| segment.category === ClapSegmentCategory.VIDEO | ||
| targetTrackInfo.name = `${segment.category}` | ||
| targetTrackInfo.occupied = true | ||
| targetTrackInfo.isPreview = isPreview | ||
| } | ||
|
|
||
| set({ | ||
| allSegmentsChanged: prevAllChanged + 1, | ||
| atLeastOneSegmentChanged: prevOneChanged + 1, | ||
| }) | ||
|
|
||
| invalidate() |
| return ( | ||
| <div className={className}> | ||
| return ( | ||
| <div className={className || 'flex h-full w-full flex-col'}> |
- createTrack: mark new typed tracks as occupied:true so they're constrained to the chosen category - createClip: add collision check before addSegment, fall back to findFreeTrack on overlap - moveSegmentToTrack: use immutable updates for segments/tracks arrays and recompute metrics so React re-renders - Timeline className: use cn() to always apply base flex layout classes and merge optional className
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the core functionality requested in #10 — the ability to create new tracks and clips directly in the timeline editor.
Changes
Timeline Store (
packages/timeline/src/hooks/useTimeline.ts):createTrack(category)— creates a new track with a specified segment category (VIDEO, IMAGE, DIALOGUE, MUSIC, SOUND, etc.), automatically configuring preview height for visual trackscreateClip({track, startTimeInMs, ...})— creates a new segment on a target track at a given time position, usingnewSegmentfrom@aitube/clapand properly assigning the output type based on categorymoveSegmentToTrack(segment, targetTrack)— validates category compatibility (same-type or empty/misc tracks), checks for time-overlap collisions, and moves the segment if validType Definitions (
packages/timeline/src/types/timeline.ts):createTrack,createClip, andmoveSegmentToTracktoTimelineStoreModifiersUI (
packages/app/src/components/core/timeline/):TimelineToolbarcomponent with category dropdown, track selector, "New Track" and "New Clip" buttonsClapTimelinecanvas in theTimelinewrapper componentRequirements addressed
createTrack()+ toolbar buttoncreateClip()+ toolbar button, clips inherit the selected categorymoveSegmentToTrack()which enforces category compatibilityTesting
bun run build:timelineandbun run build:apptype-checking both pass)build:appruntime error in/api/assistantis unrelated to these changesTest plan
.clapproject and verify the toolbar appears above the timelinemoveSegmentToTrack