Skip to content

Conversation

@ameer2468
Copy link
Collaborator

@ameer2468 ameer2468 commented Oct 27, 2025

This PR does the following:

  • Multi select clips and scenes, within their respective tracks for deleting.
  • Hovering and clicking on a studio mode recording will open the editor in the settings page.

Summary by CodeRabbit

  • New Features

    • Added multi-select capability for clips and scenes in the timeline (Ctrl/Cmd for multi-select, Shift for range selection)
    • Support for deleting multiple selected clips or scenes in a single action
    • Recording items now interactive in studio mode with click-to-edit functionality
  • Improvements

    • Layout refinements to recordings list and sidebar UI
    • Enhanced visual styling for selection states

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

Warning

Rate limit exceeded

@ameer2468 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 17 minutes and 3 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2a153d0 and a8cd0ba.

📒 Files selected for processing (2)
  • apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx (3 hunks)
  • apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx (3 hunks)

Walkthrough

The pull request refactors the timeline selection system from single-index selections to multi-index selections. Changes include updating the selection type in editor context to use indices: number[] instead of index: number, implementing multi-select and range-select behavior in track components, updating the ConfigSidebar to handle multiple segment operations, and modifying delete operations to process multiple selections in reverse order.

Changes

Cohort / File(s) Summary
Recording UI Updates
apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx
Removed padding and gap from recordings list; recording items now clickable in studio mode with conditional cursor and hover styling; badge background color changed.
Timeline Selection Type
apps/desktop/src/routes/editor/context.ts
Updated EditorState.timeline.selection to use indices: number[] instead of index: number for both clip and scene selection types.
Timeline Track Selection Logic
apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx, apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx, apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx
Added multi-select (Ctrl/Cmd) and range-select (Shift) behavior; replaced single index comparisons with indices array membership checks; updated selection state structure throughout.
Editor Configuration
apps/desktop/src/routes/editor/ConfigSidebar.tsx
Refactored to support multiple segment selections; render path switches between single and multiple segment UI; delete operations now iterate indices in reverse order.
Timeline Deletion Operations
apps/desktop/src/routes/editor/Timeline/index.tsx
Updated delete operations to iterate through selection.indices in reverse order for clips and scenes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Track as Track Component
    participant EditorState
    
    Note over User,EditorState: Single Selection (Before)
    User->>Track: Click on segment
    Track->>EditorState: setSelection({ type, index })
    
    Note over User,EditorState: Multi-Select (After)
    User->>Track: Click + Ctrl/Cmd on segment
    Track->>Track: Toggle index in existing indices
    Track->>EditorState: setSelection({ type, indices: [...] })
    
    Note over User,EditorState: Range-Select (After)
    User->>Track: Shift+Click on segment
    Track->>Track: Build range from lastIndex to current
    Track->>EditorState: setSelection({ type, indices: [range] })
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Multiple interconnected changes: The selection model refactor touches 6 files across timeline and configuration layers with coordinated logic changes.
  • Type structure cascade: The core type change in context.ts propagates through all track components and ConfigSidebar; verification needed that all usages align with new indices[] structure.
  • Event handling complexity: Track components implement multi-select and range-select logic with platform-specific modifier detection (macOS vs. others); careful review needed for correctness across platforms.
  • Delete operation order sensitivity: Multiple locations now reverse-iterate indices during deletion; critical to verify all reverse-iteration logic is correct to prevent index shifting bugs.
  • ConfigSidebar multi-segment rendering: New Show blocks and composite UI paths for multiple segments; ensure all render branches are covered.

Possibly related PRs

Suggested labels

Desktop

Suggested reviewers

  • Brendonovich

Poem

🐰 Click, shift, and control the timeline so grand,
Multiple clips now bend to my command,
Range-select hops through scenes with delight,
Multi-select magic makes edits just right!
Hop hop

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "desktop: multi-select in clip and scenes track & more" directly addresses the primary change in this PR, which is the addition of multi-select and range-select capability for clips and scenes across multiple timeline track components. This is substantiated by significant modifications to ClipTrack.tsx, SceneTrack.tsx, and related files that update the selection data structure from single indices to arrays and implement multi/range selection logic. The phrase "& more" appropriately captures the secondary changes, such as the studio mode recording behavior modifications and UI refinements in ConfigSidebar. The title is concise, specific, and avoids vague or misleading terminology.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/desktop/src/routes/editor/Timeline/index.tsx (2)

199-203: Guard against undefined bounds when moving mouse

left! may be undefined early after mount; avoid NaN previewTime.

- setEditorState(
-   "previewTime",
-   transform().position + secsPerPixel() * (e.clientX - left!),
- );
+ if (left != null) {
+   setEditorState(
+     "previewTime",
+     transform().position + secsPerPixel() * (e.clientX - left),
+   );
+ }

234-238: Fix platform detection in wheel event handler (lines 234-238)

The bug is confirmed: platform() from @tauri-apps/plugin-os is asynchronous and returns "darwin" (not "macos") on macOS, so this synchronous comparison never matches.

Use the pattern already established in the same codebase (ClipTrack.tsx lines 384–386 and SceneTrack.tsx lines 236–237):

- else if (platform() === "macos") {
-   delta = e.shiftKey ? e.deltaX : e.deltaY;
- } else {
-   delta = e.deltaY;
- }
+ else if (navigator.platform.toUpperCase().indexOf("MAC") >= 0) {
+   delta = e.shiftKey ? e.deltaX : e.deltaY;
+ } else {
+   delta = e.deltaY;
+ }

This uses the synchronous navigator.platform API (already proven in your Timeline subcomponents) and avoids the async/string mismatch.

apps/desktop/src/routes/editor/ConfigSidebar.tsx (1)

705-712: Bug: Passing accessor instead of number to ZoomSegmentPreview

Index’s index is an accessor. Passing it directly to a number prop yields NaN when used arithmetically (e.g., props.segmentIndex + 1).

- <ZoomSegmentPreview
-   segment={item().segment}
-   segmentIndex={index}
- />
+ <ZoomSegmentPreview
+   segment={item().segment}
+   segmentIndex={index()}
+ />
apps/desktop/src/routes/editor/context.ts (1)

106-116: Fix condition in deleteClipSegment (operator precedence bug)

!segment.recordingSegment === undefined is always false; it should be a direct undefined check. This currently allows deletes you intended to block.

- if (
-   !segment ||
-   !segment.recordingSegment === undefined ||
-   project.timeline.segments.filter(
-     (s) => s.recordingSegment === segment.recordingSegment,
-   ).length < 2
- )
+ if (
+   !segment ||
+   segment.recordingSegment === undefined ||
+   project.timeline.segments.filter(
+     (s) => s.recordingSegment === segment.recordingSegment,
+   ).length < 2
+ )
   return;
apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx (1)

93-99: Use currentTarget for bounds, not target

e.target may be a child element; getBoundingClientRect() can be incorrect or missing. Use the track element.

- const bounds = e.target.getBoundingClientRect()!;
+ const bounds = (e.currentTarget as Element).getBoundingClientRect();
🧹 Nitpick comments (8)
apps/desktop/src/routes/editor/Timeline/index.tsx (1)

144-156: Batch-delete helpers to reduce churn and ensure dedupe

Reverse-order loop is correct, but we already have a batched API for zoom. Mirror it for clips/scenes to dedupe, clear selection once, and minimize history churn.

Apply this refactor:

  • Add projectActions.deleteClipSegments(indices: number[]) and projectActions.deleteSceneSegments(indices: number[]) (batched, descending, deduped).
  • Replace these loops with single calls:
- // Delete all selected clips in reverse order
- [...selection.indices]
-   .sort((a, b) => b - a)
-   .forEach((idx) => {
-     projectActions.deleteClipSegment(idx);
-   });
+ projectActions.deleteClipSegments(selection.indices);
- // Delete all selected scenes in reverse order
- [...selection.indices]
-   .sort((a, b) => b - a)
-   .forEach((idx) => {
-     projectActions.deleteSceneSegment(idx);
-   });
+ projectActions.deleteSceneSegments(selection.indices);
apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx (1)

399-402: Use loop index instead of findIndex for selection

You’re already inside <For each=...>{(_, i) => ...}; avoid findIndex on every recompute.

- const segmentIndex = project.timeline?.zoomSegments?.findIndex(
-   (s) => s.start === segment.start && s.end === segment.end,
- );
- if (segmentIndex === undefined || segmentIndex === -1) return false;
- return selection.indices.includes(segmentIndex);
+ const segmentIndex = i();
+ return selection.indices.includes(segmentIndex);
apps/desktop/src/routes/editor/ConfigSidebar.tsx (2)

749-795: Batch delete scenes instead of per-index loop

Avoid per-index deleteSceneSegment calls (each clears selection) and rely on a batched API for better performance and simpler state updates.

- const indices = value().selection.indices;
- // Delete segments in reverse order to maintain indices
- [...indices].sort((a, b) => b - a).forEach((idx) => {
-   projectActions.deleteSceneSegment(idx);
- });
+ projectActions.deleteSceneSegments(value().selection.indices);

Provide deleteSceneSegments in projectActions (dedupe, bounds check, descending, clear selection once).


815-861: Batch delete clips instead of per-index loop

Same as scenes: do a single batched delete to dedupe/sort once and clear selection once.

- const indices = value().selection.indices;
- [...indices].sort((a, b) => b - a).forEach((idx) => {
-   projectActions.deleteClipSegment(idx);
- });
+ projectActions.deleteClipSegments(value().selection.indices);
apps/desktop/src/routes/editor/context.ts (1)

151-168: Add symmetric batched delete helpers for clips/scenes

You already provide deleteZoomSegments. Add deleteClipSegments and deleteSceneSegments for parity and use them from Timeline and ConfigSidebar.

Add near deleteZoomSegments:

deleteClipSegments: (segmentIndices: number[]) => {
  if (!project.timeline) return;
  batch(() => {
    setProject(
      "timeline",
      "segments",
      produce((s) => {
        if (!s) return;
        const sorted = [...new Set(segmentIndices)]
          .filter((i) => Number.isInteger(i) && i >= 0 && i < s.length)
          .sort((a, b) => b - a);
        for (const i of sorted) s.splice(i, 1);
      }),
    );
    setEditorState("timeline", "selection", null);
  });
},
deleteSceneSegments: (segmentIndices: number[]) => {
  batch(() => {
    setProject(
      "timeline",
      "sceneSegments",
      produce((s) => {
        if (!s) return;
        const sorted = [...new Set(segmentIndices)]
          .filter((i) => Number.isInteger(i) && i >= 0 && i < s.length)
          .sort((a, b) => b - a);
        for (const i of sorted) s.splice(i, 1);
      }),
    );
    setEditorState("timeline", "selection", null);
  });
},

Then update callers in Timeline/index.tsx and ConfigSidebar.tsx as suggested.

apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx (2)

245-248: Use loop index for selection membership

Avoid findIndex per recompute and rely on i().

- const segmentIndex = project.timeline?.segments?.findIndex(
-   (s) => s.start === segment.start && s.end === segment.end,
- );
- if (segmentIndex === undefined || segmentIndex === -1) return false;
- return selection.indices.includes(segmentIndex);
+ return selection.indices.includes(i());

376-451: Attach mouseup on window to avoid lost clicks

If the pointer leaves the segment before mouseup, the local target’s mouseup may not fire; selection won’t update.

- createEventListener(
-   e.currentTarget,
-   "mouseup",
-   (upEvent) => { /* ... */ }
- );
+ createEventListenerMap(window, {
+   mouseup: (upEvent) => { /* same body */ },
+ });
apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx (1)

350-353: Use loop index for selection membership

Same optimization as clips/zoom.

- const segmentIndex = project.timeline?.sceneSegments?.findIndex(
-   (s) => s.start === segment.start && s.end === segment.end,
- );
- if (segmentIndex === undefined || segmentIndex === -1) return false;
- return selection.indices.includes(segmentIndex);
+ return selection.indices.includes(i());
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85c5d69 and 2a153d0.

📒 Files selected for processing (7)
  • apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx (3 hunks)
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx (2 hunks)
  • apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx (2 hunks)
  • apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx (2 hunks)
  • apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx (1 hunks)
  • apps/desktop/src/routes/editor/Timeline/index.tsx (1 hunks)
  • apps/desktop/src/routes/editor/context.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Use strict TypeScript and avoid any; leverage shared types

Files:

  • apps/desktop/src/routes/editor/Timeline/index.tsx
  • apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx
  • apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
  • apps/desktop/src/routes/editor/context.ts
  • apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/desktop/src/routes/editor/Timeline/index.tsx
  • apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx
  • apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
  • apps/desktop/src/routes/editor/context.ts
  • apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx
apps/desktop/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/desktop/src/**/*.{ts,tsx}: Desktop icons are auto-imported (unplugin-icons); do not import icons manually
Desktop IPC: Call generated tauri_specta commands/events; listen to generated events and use typed interfaces
Use @tanstack/solid-query for server state in the desktop app

Files:

  • apps/desktop/src/routes/editor/Timeline/index.tsx
  • apps/desktop/src/routes/editor/Timeline/ZoomTrack.tsx
  • apps/desktop/src/routes/editor/Timeline/ClipTrack.tsx
  • apps/desktop/src/routes/editor/context.ts
  • apps/desktop/src/routes/editor/Timeline/SceneTrack.tsx
  • apps/desktop/src/routes/editor/ConfigSidebar.tsx
  • apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx
🧬 Code graph analysis (1)
apps/desktop/src/routes/editor/ConfigSidebar.tsx (1)
apps/desktop/src/routes/editor/ui.tsx (2)
  • Subfield (49-63)
  • EditorButton (353-418)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Lint (Biome)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (actions)

@ameer2468 ameer2468 merged commit c83f852 into main Oct 27, 2025
15 checks passed
@ameer2468 ameer2468 deleted the ux-improvements-multi-clip-select branch October 27, 2025 15:15
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