Skip to content

Fix mobile selection toolbar#3

Merged
rgarcia merged 1 commit into
mainfrom
hypeship/fix-mobile-selection-toolbar
Jun 25, 2026
Merged

Fix mobile selection toolbar#3
rgarcia merged 1 commit into
mainfrom
hypeship/fix-mobile-selection-toolbar

Conversation

@rgarcia

@rgarcia rgarcia commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • report iframe text selections from touch, pointer, keyboard, and selectionchange events so mobile browsers show the comment/reaction toolbar
  • keep the selection toolbar clamped inside the visible document pane

Tests

  • npm test
  • npm run build

Note

Low Risk
UI-only changes to selection event handling and toolbar positioning in the comments overlay; no API or auth impact.

Overview
Mobile browsers often finalize text selection without a reliable desktop-style mouseup, so the comment/reaction toolbar never appeared. The iframe overlay now reports selection through a shared reportSelection path debounced by queueSelectionReport, listening to mouseup, keyup, touchend, pointerup, and selectionchange (with event-specific delays). Clicks on overlay chips/popovers are still ignored via fromOverlayChrome, and failures clear selection with jh:selectionCleared.

In CommentsShell, the floating SelectionToolbar no longer uses a simple minimum top offset; top is a CSS max/min expression so the toolbar stays within the visible doc pane (roughly 84px from the bottom).

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

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
justhtml Ready Ready Preview, Comment Jun 25, 2026 8:47am

@rgarcia rgarcia marked this pull request as ready for review June 25, 2026 08:48
@rgarcia rgarcia merged commit 262e0a8 into main Jun 25, 2026
5 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Shared timer drops intended delays
    • Selection reporting now tracks a pending deadline and ignores shorter reschedules so fast follow-up events cannot cut off longer intended waits.
  • ✅ Fixed: Selectionchange ignores overlay chrome
    • Selection reporting now detects ranges inside overlay chrome ([data-jh-chip]/.jh-pop) and emits jh:selectionCleared instead of posting document selection payloads.

Create PR

Or push these changes by commenting:

@cursor push a664bef300
Preview (a664bef300)
diff --git a/lib/docs/overlay.ts b/lib/docs/overlay.ts
--- a/lib/docs/overlay.ts
+++ b/lib/docs/overlay.ts
@@ -570,13 +570,25 @@
     return { exact: exact, prefix: tm.full.slice(Math.max(0, s-32), s), suffix: tm.full.slice(e, e+32) };
   }
   var selectionTimer = null;
+  var selectionTimerDueAt = 0;
   function fromOverlayChrome(ev){
     var t = ev && ev.target;
     return !!(t && t.closest && (t.closest("[data-jh-chip]") || t.closest(".jh-pop")));
   }
+  function isSelectionInOverlayChrome(sel){
+    if (!sel || !sel.rangeCount) return false;
+    var r = sel.getRangeAt(0);
+    function inChrome(node){
+      if (!node) return false;
+      var el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
+      return !!(el && el.closest && (el.closest("[data-jh-chip]") || el.closest(".jh-pop")));
+    }
+    return inChrome(r.startContainer) || inChrome(r.endContainer) || inChrome(r.commonAncestorContainer);
+  }
   function reportSelection(){
     try {
       var sel = window.getSelection();
+      if (isSelectionInOverlayChrome(sel)){ send({type:"jh:selectionCleared"}); return; }
       if (!sel || !sel.rangeCount || sel.isCollapsed || !sel.toString().trim()){ send({type:"jh:selectionCleared"}); return; }
       var anchor = anchorFromSelection(sel);
       var rect = sel.getRangeAt(0).getBoundingClientRect();
@@ -589,11 +601,15 @@
     }
   }
   function queueSelectionReport(delay){
+    var dueAt = Date.now() + delay;
+    if (selectionTimer && dueAt <= selectionTimerDueAt) return;
     if (selectionTimer) clearTimeout(selectionTimer);
+    selectionTimerDueAt = dueAt;
     selectionTimer = setTimeout(function(){
       selectionTimer = null;
+      selectionTimerDueAt = 0;
       reportSelection();
-    }, delay);
+    }, Math.max(0, selectionTimerDueAt - Date.now()));
   }
   document.addEventListener("mouseup", function(ev){
     if (fromOverlayChrome(ev)) return;

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit b511f84. Configure here.

Comment thread lib/docs/overlay.ts
queueSelectionReport(30);
});
document.addEventListener("selectionchange", function(){
queueSelectionReport(120);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shared timer drops intended delays

High Severity

The queueSelectionReport function's single shared timer means events with shorter delays can prematurely cancel and override pending selection reports from events with longer delays. This can cause selection to be read too early, particularly on touch devices where events are synthesized, leading to jh:selectionCleared or a stale selection rect before the selection fully settles.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b511f84. Configure here.

Comment thread lib/docs/overlay.ts
queueSelectionReport(30);
});
document.addEventListener("selectionchange", function(){
queueSelectionReport(120);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Selectionchange ignores overlay chrome

Medium Severity

The new selectionchange listener always schedules reportSelection, but mouseup, touchend, and pointerup skip events from [data-jh-chip] and .jh-pop via fromOverlayChrome. Text selected inside a .jh-pop can still post jh:selection and show the parent comment/reaction toolbar for overlay UI, not document content.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b511f84. Configure here.

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.

1 participant