Skip to content

refactor: decompose ClipBlock.tsx into focused sub-components#1052

Merged
ChuxiJ merged 1 commit intomainfrom
refactor/issue-1026-clipblock
Mar 27, 2026
Merged

refactor: decompose ClipBlock.tsx into focused sub-components#1052
ChuxiJ merged 1 commit intomainfrom
refactor/issue-1026-clipblock

Conversation

@ChuxiJ
Copy link
Copy Markdown

@ChuxiJ ChuxiJ commented Mar 27, 2026

Summary

Closes #1026

Decomposes ClipBlock.tsx from 1481 lines down to 486 lines (67% reduction) by extracting focused sub-components and hooks:

  • useClipDrag.ts (566 lines) -- drag state machine (move, resize-left, resize-right, slip, scissor, range selection)
  • useClipHover.ts (85 lines) -- cursor management and hover state tracking
  • useWaveformUpgrade.ts (72 lines) -- async waveform peak upgrade effect
  • ClipFadeHandles.tsx (185 lines) -- fade in/out handles, overlays, and keyboard/mouse interactions
  • ClipDragGhost.tsx (153 lines) -- drag ghost portal rendering with cross-track highlight
  • ClipContextMenuContainer.tsx (161 lines) -- context menu store wiring and callback assembly
  • ClipVersionNav.tsx (70 lines) -- version navigation buttons
  • clipPresentation.ts (41 lines) -- clip color/style computation

Pure refactor -- no behavior changes. Exported API of ClipBlock is unchanged.

Test plan

  • npx tsc --noEmit -- 0 type errors
  • npm test -- --run -- all 3008 tests pass (319 test files)
  • npm run build -- succeeds

🤖 Generated with Claude Code

Closes #1026

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 27, 2026 13:12
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

This PR refactors the timeline ClipBlock by extracting drag/hover/waveform-upgrade logic and several UI subcomponents, aiming to keep ClipBlock smaller and more focused while preserving its external API.

Changes:

  • Extracted clip drag state machine into useClipDrag and hover/cursor handling into useClipHover.
  • Extracted waveform peak upgrade effect into useWaveformUpgrade.
  • Extracted UI pieces into ClipFadeHandles, ClipDragGhost, ClipContextMenuContainer, ClipVersionNav, and clipPresentation.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/components/timeline/ClipBlock.tsx Rewired to use new hooks/components; removed in-file drag/hover/context-menu/ghost logic.
src/components/timeline/useClipDrag.ts New hook implementing clip drag/resize/slip/scissor/range-selection behavior.
src/components/timeline/useClipHover.ts New hook for hover state and cursor management.
src/components/timeline/useWaveformUpgrade.ts New hook for async waveform peak upgrade.
src/components/timeline/ClipFadeHandles.tsx Extracted fade overlays + fade handle pointer/keyboard interactions.
src/components/timeline/ClipDragGhost.tsx Extracted drag ghost portal rendering + lane highlighting.
src/components/timeline/ClipContextMenuContainer.tsx Extracted context menu wiring/handlers assembly.
src/components/timeline/ClipVersionNav.tsx Extracted version navigation UI + regenerate behavior.
src/components/timeline/clipPresentation.ts Extracted clip color/style computation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

getClipContentOffset,
getClipSourceSpan,
} from '../../utils/clipAudio';
import { ARRANGEMENT_EMPTY_TRACK_ID_PREFIX, parseArrangementEmptyTrackSlotIndex } from '../arrangement/trackSlotLayout';
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Unused import: ARRANGEMENT_EMPTY_TRACK_ID_PREFIX is imported but never used (only parseArrangementEmptyTrackSlotIndex is). Removing the unused symbol will keep the module focused and easier to scan.

Suggested change
import { ARRANGEMENT_EMPTY_TRACK_ID_PREFIX, parseArrangementEmptyTrackSlotIndex } from '../arrangement/trackSlotLayout';
import { parseArrangementEmptyTrackSlotIndex } from '../arrangement/trackSlotLayout';

Copilot uses AI. Check for mistakes.
Comment on lines 131 to 136
const handleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
if (dragRef.current || rangePreviewCommittedRef.current) {
if (rangePreviewCommittedRef.current) {
rangePreviewCommittedRef.current = false;
return;
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

handleClick no longer checks whether the last interaction was a drag (previously guarded by dragRef.current). After a move/resize drag ends with mouseup over the clip, a click can still fire and this handler will select/seek unexpectedly. Consider destructuring dragRef from useClipDrag and restoring the drag guard (or otherwise suppressing click after a drag).

Copilot uses AI. Check for mistakes.
Comment on lines 149 to 154
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
if (dragRef.current || rangePreviewCommittedRef.current) {
if (rangePreviewCommittedRef.current) {
rangePreviewCommittedRef.current = false;
return;
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

handleDoubleClick also lost the previous dragRef.current guard. If a drag gesture ends on the clip, a subsequent dblclick event can still be emitted and open the context menu / piano roll unexpectedly. Re-introduce the drag suppression here as well (e.g., via dragRef from useClipDrag).

Copilot uses AI. Check for mistakes.
import type { Clip, Track } from '../../types/project';
import { useUIStore } from '../../store/uiStore';
import { useProjectStore } from '../../store/projectStore';
import { useTransportStore } from '../../store/transportStore';
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

Unused import: useTransportStore is imported but never referenced in this hook. Removing it will reduce noise and avoid confusion about transport state dependencies.

Suggested change
import { useTransportStore } from '../../store/transportStore';

Copilot uses AI. Check for mistakes.
@ChuxiJ ChuxiJ merged commit 5b49da8 into main Mar 27, 2026
8 checks passed
ChuxiJ pushed a commit that referenced this pull request Mar 27, 2026
…erged

- Marked #1100 and #1101 as already implemented (closed)
- Merged 4 refactoring PRs: #1046, #1049, #1050, #1052
- Updated analysis to reflect current state

https://claude.ai/code/session_01KYEjWp985CYRCcSm9J46ps
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.

refactor: Decompose ClipBlock.tsx (1481 lines) into sub-components

2 participants