Skip to content

refactor(studio): isolate clip drag lifecycle - #2698

Open
miguel-heygen wants to merge 1 commit into
codex/studio-timeline-d-chromium-gate-v2from
codex/studio-timeline-d-clip-drag-lifecycle-v2
Open

refactor(studio): isolate clip drag lifecycle#2698
miguel-heygen wants to merge 1 commit into
codex/studio-timeline-d-chromium-gate-v2from
codex/studio-timeline-d-clip-drag-lifecycle-v2

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What

Extract clip-drag pointer-session lifecycle handling into one focused helper without changing drag semantics.

Why

Virtualized rows can unmount during a pointer session. The drag lifecycle needs one stable actor that owns capture, movement, cancellation, and exactly-once terminal cleanup before rendering becomes windowed.

How

  • Move pointer-session lifecycle logic out of the rendering hook.
  • Keep the existing drag mutation and snapping behavior intact.
  • Preserve explicit terminal cleanup so duplicate local/window events cannot commit twice.

This is D3 of the Family D draft review sequence. It targets current main after Family C landed; its Family D patch was replayed without semantic changes. It supersedes legacy PR #2634, which remains open for audit until landing.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (not applicable)

Validated on the exact Family D tip through the focused Timeline interaction suite, full Studio suite, typechecks, formatting, lint, file-size checks, and the strict 50,000-element Chromium gate.

2026-07-29 rebase verification

  • Replayed D1-D8 onto current main after Family C landed; git range-diff marks every old/new Family D commit exactly equivalent (=).
  • oxfmt, oxlint, changed-file size gate, Studio typecheck, and Studio Server typecheck passed.
  • Full Studio suite: 288 files passed, 3,168 tests passed, 18 todos. Full Studio Server suite: 29 files and 398 tests passed.
  • Fallow CI audit exited successfully; its five minor observations are local duplication in existing test harness setup, not duplicated production decisions.
  • Chrome 150 browser evidence: 1,000-element control passed 5/5; strict 50,000-element gate passed its 4/5 quorum with 10 mounted rows, 240 mounted clip roots, 995 timeline descendants, approved direct scrolling, and memory returned. Repeated samples showed the same host-timing outlier pattern on the exact pre-rebase control.

No merge was performed; this PR remains a draft.

@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from 259b0f1 to bad5a54 Compare July 27, 2026 20:51
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from c54611f to 5e02427 Compare July 27, 2026 20:51
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from bad5a54 to ac1bbdd Compare July 28, 2026 20:42
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from 5e02427 to c327ee5 Compare July 28, 2026 20:42
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from ac1bbdd to 3e11275 Compare July 28, 2026 22:09
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from c327ee5 to d9d7106 Compare July 28, 2026 22:09
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from 3e11275 to 81e3a70 Compare July 28, 2026 22:35
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from d9d7106 to a224fdc Compare July 28, 2026 22:35
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from 81e3a70 to dcbe193 Compare July 28, 2026 23:02
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from a224fdc to a289615 Compare July 28, 2026 23:02
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from dcbe193 to f869766 Compare July 28, 2026 23:22
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from a289615 to da1c1be Compare July 28, 2026 23:22
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from f869766 to 3467adf Compare July 28, 2026 23:53
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from da1c1be to 2913f87 Compare July 28, 2026 23:53
@miguel-heygen
miguel-heygen changed the base branch from codex/studio-timeline-d-chromium-gate-v2 to main July 29, 2026 02:06
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch 2 times, most recently from 52f5acf to b4d9414 Compare July 29, 2026 14:46
@miguel-heygen
miguel-heygen changed the base branch from main to codex/studio-timeline-d-chromium-gate-v2 July 29, 2026 15:03
@miguel-heygen
miguel-heygen marked this pull request as ready for review July 29, 2026 15:06
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from 9eaec45 to c3b04be Compare July 29, 2026 23:35
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from b4d9414 to 4ddc5bd Compare July 29, 2026 23:35

@vanceingalls vanceingalls left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: APPROVE — grade A — rubric CORRECT

Role in stack: D3 refactor — clip-drag pointer-session lifecycle extracted to a mount-only helper so D5-D8 can virtualize rows without shredding an in-flight drag.

Root-cause fit: The extracted mountTimelineClipDragGestureLifecycle attaches pointermove/pointerup/pointercancel/keydown to window (never to a row DOM node), and every piece of live state is read through refs. Callsite is useMountEffect, the sanctioned single-effect wrapper. Net: a row scroll-out cannot detach listeners or nullify state — the survival property D5-D8 leans on.

Claims verified (file:line at HEAD 4ddc5bd6):

  • Extraction only, no semantic drift: useTimelineClipDrag.ts:1-6,29 deletes local resolveTimelineDragEscape import + inline handlers, delegates at :271-296.
  • Window-scoped listeners with symmetric cleanup incl. capture:true on keydown: timelineClipDragGestureLifecycle.ts:236-249.
  • Terminal cleanup null-first-then-commit blocks double-commit on duplicate up events: commitDragPointerUp (:191-206), commitResizePointerUp (:136-172) — ref cleared BEFORE commit; a second up sees null.
  • Escape resets all four refs + group session in one pass: handleWindowKeyDown :213-232.
  • File-list/additions match PR envelope (+287/-216 → +259 new file, +28/-216 modified).

Adversarial findings:

  • P2 (note) — Non-ref params captured at mount: updateElement (Zustand, stable), restoreGroupResizeMembers (useCallback stable), React setters (stable). setShowPopover is a prop; if any parent supplies an inline function, the mount closure holds the first-render one for the hook's lifetime. Grep the caller before D5 lands to confirm stabilization.
  • P2 (note) — PR body checks "Unit tests added/updated" but diff has zero test files. Behavior is exercised by the existing Timeline interaction suite, but the row-unmount-mid-drag guarantee itself — D3's core contribution — has no explicit test.

CI state: All required checks green.

Suggested next step: Land as-is. Before D5 merges, add one focused unit test that mounts the hook, starts a drag, unmounts+remounts the row's pointerdown wiring, moves the pointer, and asserts the drag preview updates + commits — that's the invariant this PR sets up and D5-D8 will silently rely on.

Review by Via

@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from 4ddc5bd to 6958e61 Compare July 30, 2026 00:23
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from c3b04be to 287a998 Compare July 30, 2026 00:23
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Addressed at 6958e6186.

  • Verified the sole caller passes the setter returned by useState, whose identity is stable across renders; no ref indirection is needed for setShowPopover.
  • Added an explicit regression test that removes the originating row mid-gesture, then proves the window-scoped pointer move and pointer up still update and persist the drag.

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Reviewed the exact #2698 delta (287a998..6958e61), not the rest of the stack.\n\nThe extraction preserves the prior gesture semantics while moving pointermove/up/cancel and Escape handling to window scope. Listener installation/removal is symmetric, capture options match, autoscroll is stopped on terminal paths, refs are cleared before commits, and the sole caller supplies Reacts stable state setter. The new lifecycle test now removes the source row mid-gesture and proves the window-scoped move/up path still previews and commits exactly once.\n\nOne non-blocking lifecycle note: clearSuppressedClick still schedules an untracked requestAnimationFrame, so dispose cannot cancel that callback. This behavior predates the extraction, but tracking/canceling it would make the helper fully self-contained under StrictMode/remount churn.\n\nCurrent exact-head lightweight checks are green/skipped as expected; Graphite mergeability remains pending because #2697 is downstack. This PR must not land until #2697s blocker is resolved, but I found no blocker in this delta.\n\nGitHub will not let the shared author identity submit a formal approval, so recording the verdict as a PR comment.\n\nVerdict: APPROVE\nReasoning: The clip-drag lifecycle extraction is behavior-preserving, cleans up its owned global listeners, and now covers row unmount during an active gesture.\n\n— Magi

@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from 6958e61 to 9b9e15e Compare July 30, 2026 01:31
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch 2 times, most recently from abb921f to a870cf4 Compare July 30, 2026 03:15
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch 2 times, most recently from 8c1dd61 to dd56d5b Compare July 30, 2026 03:47
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-chromium-gate-v2 branch from 0011368 to 4aaf015 Compare July 30, 2026 04:05
@miguel-heygen
miguel-heygen force-pushed the codex/studio-timeline-d-clip-drag-lifecycle-v2 branch from dd56d5b to 6b92049 Compare July 30, 2026 04:05
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