Skip to content

feat: Add timeline track and clip creation (Issue #10)#136

Open
pannous wants to merge 2 commits into
jbilcke-hf:mainfrom
pannous:feat/timeline-track-clip-creation
Open

feat: Add timeline track and clip creation (Issue #10)#136
pannous wants to merge 2 commits into
jbilcke-hf:mainfrom
pannous:feat/timeline-track-clip-creation

Conversation

@pannous
Copy link
Copy Markdown

@pannous pannous commented Mar 15, 2026

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 tracks
  • createClip({track, startTimeInMs, ...}) — creates a new segment on a target track at a given time position, using newSegment from @aitube/clap and properly assigning the output type based on category
  • moveSegmentToTrack(segment, targetTrack) — validates category compatibility (same-type or empty/misc tracks), checks for time-overlap collisions, and moves the segment if valid

Type Definitions (packages/timeline/src/types/timeline.ts):

  • Added createTrack, createClip, and moveSegmentToTrack to TimelineStoreModifiers

UI (packages/app/src/components/core/timeline/):

  • New TimelineToolbar component with category dropdown, track selector, "New Track" and "New Clip" buttons
  • Integrated above the ClapTimeline canvas in the Timeline wrapper component
  • Uses existing shadcn/radix Select components and lucide-react icons for consistency

Requirements addressed

  1. Create new tracks — via createTrack() + toolbar button
  2. Set track type from dropdown — category selector in toolbar
  3. Create clips on tracks based on track type — via createClip() + toolbar button, clips inherit the selected category
  4. Drag clips on the timeline — existing functionality, unchanged
  5. Drag clips between tracks with type validation — via moveSegmentToTrack() which enforces category compatibility

Testing

  • TypeScript compiles cleanly (bun run build:timeline and bun run build:app type-checking both pass)
  • The pre-existing build:app runtime error in /api/assistant is unrelated to these changes

Test plan

  • Load a .clap project and verify the toolbar appears above the timeline
  • Select a category and click "Track" to create a new track
  • Select a track, position the cursor, and click "Clip" to create a new segment
  • Verify that clips cannot be moved to incompatible tracks via moveSegmentToTrack
  • Verify collision detection prevents overlapping clips on the same track

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
Copilot AI review requested due to automatic review settings March 15, 2026 13:16
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and moveSegmentToTrack modifiers.
  • Implemented track/clip creation and segment-to-track move validation in the timeline Zustand store.
  • Added a new TimelineToolbar UI 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants