From 27cc3f98acbd4047d232a274f2d801e647bce345 Mon Sep 17 00:00:00 2001
From: th
Date: Wed, 15 Apr 2026 21:43:12 +0200
Subject: [PATCH] tooltip: kill stuck tips with watchdog + pointermove sanity
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Tooltips could strand on screen when pointerleave didn't fire — fast
flicks off the target, re-renders that yanked the element out mid-
hover, or the tab losing focus. Three guards:
- 5s max-life watchdog on every show, so the worst case is a late
fade rather than a permanent overlay
- pointermove check: if the pointer is no longer over the tip's
owning target (or the target got detached from the DOM), hide
- window blur + visibilitychange also hide immediately
Co-Authored-By: Claude Opus 4.6 (1M context)
---
config.php | 2 +-
electron/package.json | 2 +-
public/js/modules/tooltip.js | 27 +++++++++++++++++++++++++++
3 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/config.php b/config.php
index 77c3626..d26cc3a 100644
--- a/config.php
+++ b/config.php
@@ -1,7 +1,7 @@
{ clearTimeout(showTimer); hide(); }, true);
document.addEventListener('keydown', () => { clearTimeout(showTimer); hide(); }, true);
document.addEventListener('scroll', () => { clearTimeout(showTimer); hide(); }, true);
+
+// Belt-and-suspenders sanity check on every move: if the pointer is
+// no longer over the tip-owning target (or the target has been
+// detached from the DOM since show time), hide. Catches fast flicks
+// that skip the expected pointerleave and DOM churn that yanks the
+// element out from under us.
+document.addEventListener('pointermove', (e) => {
+ if (!currentTarget) return;
+ if (!currentTarget.isConnected) { hide(); return; }
+ const el = document.elementFromPoint(e.clientX, e.clientY);
+ if (!el || !currentTarget.contains(el)) hide();
+}, true);
+
+// Window losing focus / tab hidden — nothing to hover over while
+// we're not there, kill any lingering tip.
+window.addEventListener('blur', () => { clearTimeout(showTimer); hide(); });
+document.addEventListener('visibilitychange', () => { if (document.hidden) { clearTimeout(showTimer); hide(); } });
|