From 1b66fbe99713bba0b4287b4f9c0225aece875fc0 Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 16 May 2026 21:56:59 -0400 Subject: [PATCH] fix(liturgy): recompute alignment arrow from live DOM on hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hover arrow could land at negative coordinates (off-screen / wrong position) after switching script (IAST ↔ Devanāgarī). Root cause: the `lines` state was populated by computeAlignmentLines during a transient layout — e.g. just after the script swap, before Noto Serif Devanagari fonts loaded — when the new-script word spans had zero size. Cached endpoints had `wordRect.bottom - cRect.top` = (0 - 203) = -203. The hover-time adjustment only patched the Pāli endpoint (when hovering Pāli) or the English endpoint (when hovering English) — it trusted the cache for the other end, so the bad value survived. Fix: in `adjustedLines`, recompute fresh from live DOM whenever there's a hover. By that point the DOM is settled and rects are accurate. The hover-only branch keeps the cost bounded — non-hovered renders still return the cached `lines` (which never paint anyway since AlignmentLines filters by hover). Verified: switching IAST→Deva→IAST and hovering Dhammaṁ / धम्मं in each direction now produces correctly-anchored arrows (164,49) and (127,49) instead of (-24,-203). All 284 alignment-audit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- components/liturgy/shapes/TripleScriptWitness.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/liturgy/shapes/TripleScriptWitness.tsx b/components/liturgy/shapes/TripleScriptWitness.tsx index be6a3ac..c304e9d 100644 --- a/components/liturgy/shapes/TripleScriptWitness.tsx +++ b/components/liturgy/shapes/TripleScriptWitness.tsx @@ -932,9 +932,15 @@ const SegmentRow: React.FC<{ // one morpheme's centre, collapsing the auto-distributed fan. const adjustedLines = (() => { if (!hovered || !containerRef.current) return lines; + // Recompute lines fresh from current DOM — defensive against a stale + // `lines` cache. The cache can be wrong when computeAlignmentLines + // ran during a transient state (e.g. script swap before Devanāgarī + // fonts loaded), giving zero-size element rects and negative-space + // coordinates. We're hovering now, so the DOM is settled. + const fresh = computeAlignmentLines(containerRef.current, currentWitness?.alignTo); const cRect = containerRef.current.getBoundingClientRect(); const r = hovered.element.getBoundingClientRect(); - return lines.map((l) => { + return fresh.map((l) => { if ( hovered.kind === 'pali' && l.paliIdx === hovered.idx &&