Skip to content

fix(web): stop the resize cursor sticking when the panel changes mid-drag - #8503

Closed
alundgren wants to merge 6 commits into
pingdotgg:mainfrom
alundgren:t3code/reproduce-resize-cursor-bug
Closed

fix(web): stop the resize cursor sticking when the panel changes mid-drag#8503
alundgren wants to merge 6 commits into
pingdotgg:mainfrom
alundgren:t3code/reproduce-resize-cursor-bug

Conversation

@alundgren

@alundgren alundgren commented Aug 28, 2026

Copy link
Copy Markdown

Problem

The app can get stuck showing the col-resize cursor everywhere in the main content area, with text selection disabled alongside it. It persists for the rest of the session. Moving the pointer outside the window restores the normal cursor, and coming back shows the resize cursor again.

useResizableWidth() sets cursor: col-resize and user-select: none on document.body when a drag starts, and only clears them from onPointerUp and onPointerCancel. PreviewPanelShell owns the hook but renders the drag handle only while the panel is inline and not maximized. Maximizing the panel (rightPanel.toggleMaximized), switching its mode, or closing it therefore removes the handle mid-drag. After that no pointer handler can ever run, and both global styles stay behind.

Fix

Clean up from a ref callback on the drag handle, reusing the existing releasePointer(). React runs it when the element leaves the DOM, so it covers both the handle being removed while the hook stays mounted and the panel unmounting outright, and it cannot drift out of sync with the condition that renders the handle. releasePointer() already no-ops without an active drag and tolerates a double release, so normal pointer-up and pointer-cancel are unaffected and the final width still persists.

Tests

useResizableWidth had no coverage. Added useResizableWidth.test.tsx, using the DOM-free React harness this suite already uses in components/preview/PreviewView.test.tsx. The harness mirrors PreviewPanelShell: the hook lives in the parent and the handle is rendered conditionally beneath it.

Three cases: a normal pointer-down/pointer-up drag, the handle being removed while the hook stays mounted, and the whole panel unmounting mid-drag. The last two fail on main with the body still holding col-resize.

  • vp test run apps/web/src/hooks/useResizableWidth.test.tsx
  • vp test run apps/web/src/components/preview/PreviewPanelShell.test.ts apps/web/src/components/preview/PreviewView.test.tsx
  • vp run --filter @t3tools/web typecheck
  • targeted lint for the changed files

Visual evidence

None. The symptom is the shape of the OS pointer, which a screenshot does not capture.

Not covered

Whether pointer capture can also be lost without the handle being removed, for example on window blur or a native menu opening mid-drag.

Made with Claude Opus 5 in Claude Code.

Note

Fix resize cursor sticking when panel closes mid-drag in useResizableWidth

  • The useResizableWidth hook now exposes a ref callback on its returned handlers object. Attaching it to the drag handle element ensures that an active drag is terminated and global cursor/user-select styles are cleared if the handle unmounts mid-drag.
  • The cleanup function returned by the ref calls releasePointer(state.pointerId) when the element is removed while dragging.
  • New tests in useResizableWidth.test.tsx verify style cleanup on pointer up, handle unmount mid-drag, and full panel unmount mid-drag.
  • Behavioral Change: ResizableWidthHandlers now requires consumers to attach the new ref to the drag handle; existing consumers that do not wire up the ref will not get the mid-drag unmount cleanup.

Macroscope summarized b8702f4.


Note

Low Risk
Localized UI/pointer-drag cleanup with idempotent release logic; no auth, data, or API changes.

Overview
Fixes a session-long col-resize cursor and disabled text selection when the preview panel is maximized, closed, or otherwise removes its drag handle while a resize drag is still active. Previously, global document.body styles were only cleared on pointer up/cancel, so unmounting the handle left no handler to run.

useResizableWidth now returns a ref callback on handlers that, on handle unmount, calls the existing releasePointer path to drop pointer capture and remove body cursor / user-select. Consumers that spread handlers onto the handle (e.g. RightPanelResizeHandle) pick this up without extra wiring.

Adds useResizableWidth.test.tsx with a DOM-free React harness (aligned with preview tests): normal pointer-up cleanup, handle removed mid-drag, and full unmount mid-drag.

Reviewed by Cursor Bugbot for commit b8702f4. Bugbot is set up for automated code reviews on this repo. Configure here.

…drag

useResizableWidth sets cursor and user-select on document.body when a drag
starts and only clears them from onPointerUp and onPointerCancel. The drag
handle unmounts mid-drag when the panel is maximized, switched out of inline
mode, or closed, after which no pointer handler can run and both global styles
stay behind for the rest of the session.

Clean up on unmount when a drag is still in flight, and add regression coverage
for the hook.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 28, 2026
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a556684d-8d6b-4ab0-8271-b54153eb1f9a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


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

Comment thread apps/web/src/hooks/useResizableWidth.ts Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at eb13b55

Macroscope's review found this PR approvable — This is a narrowly scoped web bug fix that ties existing resize cleanup to the drag handle's lifecycle, preventing stale global cursor and selection styles when the panel changes mid-drag. Dedicated regression tests cover both interrupted and normal drag lifecycles without introducing broader production behavior.

You can add or adjust custom eligibility rules. Learn more.

PreviewPanelShell owns the hook but renders the handle only while the panel is
inline and not maximized, so maximizing removes the handle without unmounting
the hook. The unmount effect never ran for that case, which is the most common
way the drag is interrupted.

Clean up from a ref callback on the handle instead. It runs when the element
leaves the DOM, covering both handle removal and full unmount, and cannot drift
out of sync with the condition that renders the handle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alundgren alundgren changed the title fix(web): stop the resize cursor sticking after the panel closes mid-drag fix(web): stop the resize cursor sticking when the panel changes mid-drag Aug 28, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Superseded by #10461, which landed on main.

That PR clears the stuck col-resize cursor / text-selection lock from useResizableWidth when a panel resize is interrupted (unmount, lost pointer capture, or window blur), which is the same mid-drag stuck-cursor bug this PR targets. Closing this as superseded — please reopen if anything here is still needed beyond that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants