From 23154ac284dea27b07f6efc02e9b81469a78878a Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Jul 2026 11:18:49 -0400 Subject: [PATCH] fix: pane scroll on NEWLY-active annotation (set diff) Track the full set of annotations the playhead is inside and diff against last tick; scroll only for newly-entered ones, so a permanently-active annotation (e.g. a full-timeline one) counts as new just once and stops hijacking the scroll. Pick one deterministically among the newly active (greatest lo, then tightest bar). Co-Authored-By: Claude Opus 4.8 --- tl/src/tl/views.cljs | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/tl/src/tl/views.cljs b/tl/src/tl/views.cljs index 2fa9ab6..8993bf5 100644 --- a/tl/src/tl/views.cljs +++ b/tl/src/tl/views.cljs @@ -15,7 +15,7 @@ (defonce body-scroll-el (atom nil)) ; the vertical (tracks) scroll container (defonce playhead-el (atom nil)) ; the single full-height playhead overlay (defonce played-bar (atom nil)) ; [ann-id bar-idx] last auto-scrolled to during playback -(defonce scrolled-ann (atom nil)) ; ann-id the commentary pane last auto-scrolled to +(defonce active-ann-set (atom #{})) ; ann-ids whose bar the playhead was inside last tick (defonce scrolled-on-load? (atom nil)) ; did we scroll to deep-linked playhead on load (defonce play (atom nil)) ; {:ctx :segs :fps :idx} while playing, else nil (defonce active-insert! (atom nil)) ; the live content-editor's (insert! link) fn @@ -488,25 +488,28 @@ (scroll-to-seg-track! segs playhead))))) (do (reset! played-bar nil) (reset! scrolled-on-load? nil))) - ;; scroll the commentary pane to the annotation the playhead is inside. ONE - ;; deterministic pick, so overlapping annotations don't race: the MOST - ;; RECENTLY ENTERED one — the active bar with the greatest lo (ties → the - ;; tighter bar). A full-timeline annotation (lo 0) thus only wins when nothing - ;; more specific contains the playhead. Fires on any playhead move. - (let [primary (->> anns - (keep (fn [a] - (some (fn [[lo hi]] - (when (and (<= lo playhead) (< playhead hi)) [lo hi (:id a)])) - (:bars a)))) - (sort-by (fn [[lo hi _]] [lo (- hi)])) - last - (#(nth % 2 nil)))] - (when (not= primary @scrolled-ann) - (reset! scrolled-ann primary) - (when primary - (r/after-render - #(when-let [node (js/document.getElementById (str "ann-" (name primary)))] - (.scrollIntoView node #js {:block "center" :behavior "smooth"})))))) + ;; scroll the commentary pane when the playhead ENTERS an annotation. Track the + ;; full set of active annotations and diff against last tick: scroll only for + ;; NEWLY active ones, so a permanently-active annotation (e.g. one spanning the + ;; whole timeline) is "new" just once and never hijacks the scroll. Among the + ;; newly active, pick one deterministically: greatest lo (tightest entry), then + ;; smallest bar. Fires on any playhead move (playback or scrub). + (let [active-bars (keep (fn [a] + (when-let [[lo hi] (some (fn [[lo hi]] + (when (and (<= lo playhead) (< playhead hi)) [lo hi])) + (:bars a))] + {:id (:id a) :lo lo :hi hi})) + anns) + active (into #{} (map :id) active-bars) + newly (into #{} (remove @active-ann-set) active)] + (reset! active-ann-set active) + (when-let [pick (->> active-bars + (filter (comp newly :id)) + (sort-by (juxt (comp - :lo) :hi)) + first :id)] + (r/after-render + #(when-let [node (js/document.getElementById (str "ann-" (name pick)))] + (.scrollIntoView node #js {:block "center" :behavior "smooth"}))))) (r/after-render #(position-playhead! fps zoom playhead)) nil))