Skip to content

fix(record-tour): make the Record Map Tour close button work (#876)#878

Merged
giswqs merged 2 commits into
mainfrom
fix/issue-876-record-tour-close
Jun 25, 2026
Merged

fix(record-tour): make the Record Map Tour close button work (#876)#878
giswqs merged 2 commits into
mainfrom
fix/issue-876-record-tour-close

Conversation

@giswqs

@giswqs giswqs commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

The close (X) button in the Record Map Tour panel did nothing, so users could not exit the recording window (#876).

Root cause

The close button lives inside the panel's drag-handle title bar. That title bar has an onPointerDown handler that begins a drag and calls setPointerCapture(pointerId). Pressing the X bubbled the pointerdown up to the title bar, which captured the pointer; with the pointer captured by the title bar, the subsequent click was dispatched to the title bar div instead of the button, so the button's onClick never fired and the panel stayed open.

Fix

Bail out of onDragStart when the pointer goes down on an interactive control (the close button), so no pointer capture happens and the click reaches the button normally. Dragging the panel by the grip/title still works.

Verification

Reproduced and verified in the real app with Playwright:

  • Before: clicking Close left the dialog open.
  • After: clicking Close closes the dialog, in both light and dark themes (real pointer click, not a synthetic .click()).
  • Dragging the panel by the title bar still repositions it.
  • pre-commit run --files ... passes (eslint + full npm build).

Fixes #876

Summary by CodeRabbit

  • Bug Fixes
    • Improved dialog drag behavior so clicking interactive controls inside the drag handle no longer starts a drag.
    • Fixed an issue where button clicks in the dialog header could be blocked, making controls like close buttons respond reliably.

The close (X) button lives inside the dialog's drag-handle title bar, whose
onPointerDown starts a drag and calls setPointerCapture. Pressing the X
therefore captured the pointer on the title bar, redirecting the ensuing click
away from the button so its onClick never fired and the panel could not be
closed.

Bail out of the drag when the pointer goes down on an interactive control (the
close button), so no capture happens and the click reaches the button.
Dragging from the grip/title still works.

Fixes #876
@netlify

netlify Bot commented Jun 25, 2026

Copy link
Copy Markdown

Deploy Preview for geolibre-app ready!

Name Link
🔨 Latest commit 3186ad9
🔍 Latest deploy log https://app.netlify.com/projects/geolibre-app/deploys/6a3d6c96c25a59000888f0f7
😎 Deploy Preview https://deploy-preview-878--geolibre-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Cloudflare Pages preview

Item Value
Preview https://9d3acaa9.geolibre-preview.pages.dev
Demo app https://9d3acaa9.geolibre-preview.pages.dev/demo/
Commit e2058a5

Comment on lines +101 to +104
// The close button lives inside this drag handle. Starting a drag here
// captures the pointer, which would redirect the ensuing click away from
// the button and swallow it, so never begin a drag from an interactive
// control.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The first sentence describes DOM structure (the "what"), which per CLAUDE.md should be left out of comments. The pointer-capture explanation is the valuable "why". Consider trimming to one tight line:

Suggested change
// The close button lives inside this drag handle. Starting a drag here
// captures the pointer, which would redirect the ensuing click away from
// the button and swallow it, so never begin a drag from an interactive
// control.
// setPointerCapture would redirect the subsequent click away from the button's onClick; bail out so the button works normally.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 3186ad9 — trimmed the comment to lead with the pointer-capture 'why' and dropped the DOM-structure sentence.

// captures the pointer, which would redirect the ensuing click away from
// the button and swallow it, so never begin a drag from an interactive
// control.
if ((event.target as HTMLElement).closest("button")) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two minor nits to group:

  1. TypeScript cast accuracy (low confidence): event.target on a pointer event that originates from the SVG <X> icon is an SVGElement, not an HTMLElement. Both have .closest() (inherited from Element), so it works, but Element is the more accurate common ancestor type: (event.target as Element).

  2. Selector breadth (low confidence, forward-looking): The guard only matches <button> elements by tag. If a link or [role="button"] were ever added to the drag handle the same pointer-capture bug would recur. A slightly broader selector is more defensive:

Suggested change
if ((event.target as HTMLElement).closest("button")) return;
if ((event.target as Element).closest("button, a, [role='button']")) return;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Both applied in 3186ad9: cast to Element (the pointerdown can originate on the SVG icon, not an HTMLElement) and broadened the selector to button, a, [role='button'] so the guard stays robust if another control is ever added to the drag handle.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@giswqs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 8 minutes and 55 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1e592180-b482-48a2-84c2-2d5c4d16a28d

📥 Commits

Reviewing files that changed from the base of the PR and between 3a33d84 and 3186ad9.

📒 Files selected for processing (1)
  • apps/geolibre-desktop/src/components/layout/RecordTourDialog.tsx
📝 Walkthrough

Walkthrough

RecordTourDialog now ignores pointer-down events that begin on nested buttons inside the drag handle, allowing those buttons to receive clicks without starting a drag.

Changes

RecordTourDialog drag handling

Layer / File(s) Summary
Skip button-originated drags
apps/geolibre-desktop/src/components/layout/RecordTourDialog.tsx
The drag-start handler returns early when the pointer event target is a nested <button> inside the drag handle.

🎯 1 (Trivial) | ⏱️ ~2 minutes

🐇 I tapped the little X, hop-hop hooray,
The drag now steps aside and lets clicks have their say.
No tug, no snare, just a button’s bright cheer,
The window can close without dragging me near.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: restoring the Record Map Tour close button.
Linked Issues check ✅ Passed The change addresses issue #876 by preventing the drag handle from blocking the close button click.
Out of Scope Changes check ✅ Passed The diff is narrowly scoped to the close-button/drag-handle interaction and adds no unrelated changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-876-record-tour-close

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.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

This is a small, targeted bug fix (5 lines). The root cause diagnosis in the PR description is correct — setPointerCapture on the drag-handle div was stealing the pointer before the button's click could fire — and the guard at the top of onDragStart correctly breaks that chain. The existing drag, drop, and boundary-clamping logic is untouched. Overall the fix is sound.

Bugs

  • None found. The closest("button") guard correctly handles the case where event.target is the SVG <X> icon rather than the <button> element itself, because closest() traverses upward through ancestors. The disabled-while-recording state interacts cleanly: a click on the disabled close button still hits the early return (drag doesn't start), and the button's onClick doesn't fire because the button is disabled — correct behaviour in both states.

Security

  • Nothing to flag. No new inputs, no data handling changes.

Performance

  • Nothing to flag. One cheap DOM traversal per pointerdown on the title bar.

Quality

  • Comment verbosity (high confidence): The 4-line block partially describes DOM structure ("The close button lives inside this drag handle") rather than purely the non-obvious why. CLAUDE.md says comments should explain only the non-obvious why. The pointer-capture redirection is genuinely surprising and worth a comment, but it can be captured in one tight line. See inline suggestion.
  • TypeScript cast (low confidence): event.target as HTMLElement — the actual target when clicking the <X> icon is an SVGElement. Both share .closest() via Element, so it works, but Element is the accurate common ancestor. See inline note.
  • Selector breadth (low confidence, forward-looking): closest("button") only guards <button> tags. An <a> or [role="button"] added to the drag handle in the future would reintroduce the same bug. A broader selector (or adding stopPropagation on future interactive children) would be more defensive. Not a problem today. See inline note.

CLAUDE.md

  • Comment style: the 4-line comment partially violates the "don't explain WHAT the code does" guideline (see Quality above).

Cast event.target to Element (the pointerdown can originate on the SVG icon
inside the button, not an HTMLElement) and broaden the guard to also skip
links and role=button controls, so the same pointer-capture issue can't recur
if another interactive control is added to the drag handle.
// click away from the close button and swallow it, so never begin a drag
// from an interactive control. (event.target may be the SVG icon inside the
// button, so cast to the Element ancestor type rather than HTMLElement.)
if ((event.target as Element).closest("button, a, [role='button']")) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The selector covers the current drag-handle content (only the close <button> and its <X> SVG child are in there), so the fix is correct as written.

Minor forward-compatibility nit: if someone later adds an <input>, <select>, or <textarea> to the title bar, those wouldn't be caught and the drag would capture the pointer mid-edit. Broadening the guard costs nothing:

Suggested change
if ((event.target as Element).closest("button, a, [role='button']")) return;
if ((event.target as Element).closest("button, a, input, select, textarea, [role='button']")) return;

Not a bug today — just worth knowing if the drag handle ever grows.

@github-actions

Copy link
Copy Markdown
Contributor

Code review

Reviewed the single changed file (RecordTourDialog.tsx, +5 lines / 0 deletions). Read the full component for context, and traced the event-dispatch path through onDragStartsetPointerCaptureonDragEnd / releasePointerCapture.

Bugs

  • None found. The root-cause analysis in the PR description is accurate: setPointerCapture on the drag handle was redirecting the close-button click to the title bar div. The early-return guard fixes it correctly.

Security

  • None found.

Performance

  • None found. Element.closest() runs once per pointerdown on the title bar; cost is negligible.

Quality

  • Selector completeness (low confidence / forward-looking): The "button, a, [role='button']" selector is correct for the current drag-handle markup (only a <button> lives there today). input, select, and textarea are absent from the guard, so a future drag-handle control would silently regress. Inline suggestion posted at line 105 with a one-word fix. No action required for this PR.
  • Implementation is otherwise clean: using event.target (not event.currentTarget) with .closest() correctly handles the SVG-icon-inside-button case described in the comment. The Element cast is safe because DOM pointer events never target text nodes. The early return leaves dragOffset.current as null, so onDragMove short-circuits and onDragEnd's releasePointerCapture call on a non-captured pointer is a spec-defined no-op — no stale state or exception risk.

CLAUDE.md

  • No applicable guidelines violated. The change is isolated to a single component, adds no new UI strings, and touches no Tauri/tile-host/CSP-listed concerns.

Overall: The fix is correct and minimal. One low-confidence selector-completeness note posted as an inline suggestion; everything else looks good.

@giswqs giswqs merged commit 7faa912 into main Jun 25, 2026
23 checks passed
@giswqs giswqs deleted the fix/issue-876-record-tour-close branch June 25, 2026 18:40
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.

Bug report: Record Window Close Button does not work

1 participant