Skip to content

Hover over comment highlights to open thread popovers#105

Open
HamptonMakes wants to merge 2 commits intomainfrom
hampton/hover-comment-highlight
Open

Hover over comment highlights to open thread popovers#105
HamptonMakes wants to merge 2 commits intomainfrom
hampton/hover-comment-highlight

Conversation

@HamptonMakes
Copy link
Copy Markdown
Collaborator

Why

Today you have to click an inline comment highlight (or its margin dot) to read the thread. Hovering should be enough — opening on hover makes the discussion feel ambient instead of hidden, especially when scanning a doc.

What

  • Hover a <mark> → popover opens after 350 ms
  • Leave the mark → popover closes after 250 ms (cancelled if cursor enters the popover or another mark)
  • Click on a mark / open via j/k = pinned (stays until Esc, click outside, or native light-dismiss)
  • Hover never stomps a pinned popover (DOM-level :popover-open check coordinates with comment_nav_controller.js)
  • Switching between hover popovers is instant (tooltip-group feel)
  • All in one file: text_selection_controller.js

Risk Assessment

Low — JS-only change to a single Stimulus controller, no model/route/CSS changes, no behavior removed. Click and j/k navigation paths are preserved (verified by walking the code, not just the diff).

References

Generated with Amp

Add hover-to-open behavior to inline comment <mark> elements:

- 350ms hover-open delay (avoids triggering on casual reading)
- 250ms hover-close delay on mouseleave (gives cursor time to enter
  the popover); cancelled if cursor enters either the popover or
  another mark for the same thread
- Hovering a different mark while a hover popover is open switches
  immediately (no re-delay), like a tooltip group
- Click-opened and j/k-nav-opened popovers are 'pinned' — they stay
  until explicitly dismissed (Esc, click outside, native light-dismiss)
- Hover does nothing if any popover is already pinned (avoids flicker)
- DOM-level :popover-open check so we coordinate correctly with
  comment_nav_controller (which doesn't go through this controller)

Generated with Amp

Amp-Thread-ID: https://ampcode.com/threads/T-019df459-b110-726a-97e2-ff15e2903435
Co-authored-by: Amp <amp@ampcode.com>
@HamptonMakes HamptonMakes marked this pull request as ready for review May 4, 2026 20:26
@HamptonMakes HamptonMakes requested a review from Copilot May 4, 2026 20:26
@HamptonMakes
Copy link
Copy Markdown
Collaborator Author

@codex

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9550ff59f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +238 to +244
const wasOpen = this._activePopover === popover
if (!wasOpen) {
popover.style.visibility = "hidden"
try {
popover.showPopover()
} catch {
// Already open — fall through to repositioning.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reopen dismissed popovers instead of trusting cached state

After a thread popover is light-dismissed (Esc/click outside), _activePopover is not cleared immediately, so this branch can treat the same popover as already open and skip showPopover(). In that state, clicking or hovering the same highlight will not reopen its thread until some other path (like scrolling) resets controller state, which breaks the primary interaction loop for a thread.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — fixed in 75012e3. Added a 'toggle' event listener (bound once per popover element via _coplanToggleBound) that clears _activePopover/_activeMark/_openMode whenever newState === 'closed'. So Esc, click-outside, and any other native light-dismiss path now reset state; re-clicking or hovering the same highlight reopens the thread.

Copy link
Copy Markdown
Contributor

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

Adds hover-to-open behavior for inline comment highlight popovers in Coplan’s plan doc review UI, aiming to make discussion threads discoverable without clicks while preserving existing click and keyboard navigation behaviors.

Changes:

  • Introduces “hover” vs “pinned” open modes for thread popovers, with delayed open (350ms) and delayed close (250ms).
  • Adds hover enter/leave handlers on anchor <mark> elements and hover listeners on popovers to keep them open when moving the cursor into the popover.
  • Coordinates with existing j/k navigation by checking for any currently-open .thread-popover:popover-open in the DOM.

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

Comment on lines +307 to +313
// Re-check guards in case state changed during the delay. Don't open
// if any other popover is currently pinned (click or j/k).
if (this._openMode === "pinned" && this._activePopover) return
const ourPopover = document.getElementById(`${mark.dataset.threadId}_popover`)
const openInDom = document.querySelector(".thread-popover:popover-open")
if (openInDom && openInDom !== ourPopover) return
this._showThreadPopoverFor(mark, "hover")
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same root cause as the Codex P1 above — fixed in 75012e3 the way you suggested: a 'toggle' event listener now clears _openMode/_activePopover whenever the popover transitions to closed. So light-dismiss of a pinned popover no longer permanently blocks future hover-opens. Listener is bound once per popover (_coplanToggleBound flag) so it survives re-positioning and broadcast updates.

Comment on lines +282 to +287
_isAnyPopoverOpen() {
try {
return !!document.querySelector(".thread-popover:popover-open")
} catch {
return false
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 75012e3. Replaced _isAnyPopoverOpen() with a _findOpenPopover() helper that mirrors comment_nav_controller#findOpenPopover — same try/catch around :popover-open with a checkVisibility() fallback. The inline document.querySelector in _scheduleHoverOpen now goes through that helper too, so both checks degrade gracefully on browsers without :popover-open support.

Comment on lines 494 to +498
mark.dataset.threadId = threadId
mark.style.cursor = "pointer"
mark.addEventListener("click", (e) => this.openThreadPopover(e))
mark.addEventListener("mouseenter", () => this.handleMarkHoverEnter(mark))
mark.addEventListener("mouseleave", () => this.handleMarkHoverLeave(mark))
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Skipping the system spec for this PR — Hampton (the author) and I agreed the timing/feel parts are best validated by use, and the bug-class issues you flagged above are now covered by the controller's own state machine. We'll add coverage if regressions show up. Tracked separately if it becomes recurrent.

Codex (P1) and Copilot both flagged: when a popover is closed by native
light-dismiss (Esc, click outside, another popover='auto' opening), our
controller never learned about it, so _activePopover/_openMode stayed
populated. The 'pinned popover already open' guard then blocked
hover-open forever, and clicking the same mark again would skip
showPopover() because _activePopover === popover (treated as 'still open').

Fix: listen to the popover 'toggle' event and clear all tracked state
when newState === 'closed'. The listener is bound once per popover
element via a _coplanToggleBound flag.

Also addressed Copilot's third comment: _isAnyPopoverOpen() now uses
the same fallback as comment_nav_controller#findOpenPopover (handles
browsers where the :popover-open selector throws), and the inline
querySelector in _scheduleHoverOpen now goes through the same helper.
Removed the now-redundant _openMode === 'pinned' guard there since the
DOM check is the source of truth and toggle clears stale state.

Skipping the system spec suggestion for now per user direction.

Generated with Amp

Amp-Thread-ID: https://ampcode.com/threads/T-019df459-b110-726a-97e2-ff15e2903435
Co-authored-by: Amp <amp@ampcode.com>
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