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 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-07-03 11:18:49 -04:00
parent 183e776727
commit 23154ac284

View file

@ -15,7 +15,7 @@
(defonce body-scroll-el (atom nil)) ; the vertical (tracks) scroll container (defonce body-scroll-el (atom nil)) ; the vertical (tracks) scroll container
(defonce playhead-el (atom nil)) ; the single full-height playhead overlay (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 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 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 play (atom nil)) ; {:ctx :segs :fps :idx} while playing, else nil
(defonce active-insert! (atom nil)) ; the live content-editor's (insert! link) fn (defonce active-insert! (atom nil)) ; the live content-editor's (insert! link) fn
@ -488,25 +488,28 @@
(scroll-to-seg-track! segs playhead))))) (scroll-to-seg-track! segs playhead)))))
(do (reset! played-bar nil) (do (reset! played-bar nil)
(reset! scrolled-on-load? nil))) (reset! scrolled-on-load? nil)))
;; scroll the commentary pane to the annotation the playhead is inside. ONE ;; scroll the commentary pane when the playhead ENTERS an annotation. Track the
;; deterministic pick, so overlapping annotations don't race: the MOST ;; full set of active annotations and diff against last tick: scroll only for
;; RECENTLY ENTERED one — the active bar with the greatest lo (ties → the ;; NEWLY active ones, so a permanently-active annotation (e.g. one spanning the
;; tighter bar). A full-timeline annotation (lo 0) thus only wins when nothing ;; whole timeline) is "new" just once and never hijacks the scroll. Among the
;; more specific contains the playhead. Fires on any playhead move. ;; newly active, pick one deterministically: greatest lo (tightest entry), then
(let [primary (->> anns ;; smallest bar. Fires on any playhead move (playback or scrub).
(keep (fn [a] (let [active-bars (keep (fn [a]
(some (fn [[lo hi]] (when-let [[lo hi] (some (fn [[lo hi]]
(when (and (<= lo playhead) (< playhead hi)) [lo hi (:id a)])) (when (and (<= lo playhead) (< playhead hi)) [lo hi]))
(:bars a)))) (:bars a))]
(sort-by (fn [[lo hi _]] [lo (- hi)])) {:id (:id a) :lo lo :hi hi}))
last anns)
(#(nth % 2 nil)))] active (into #{} (map :id) active-bars)
(when (not= primary @scrolled-ann) newly (into #{} (remove @active-ann-set) active)]
(reset! scrolled-ann primary) (reset! active-ann-set active)
(when primary (when-let [pick (->> active-bars
(r/after-render (filter (comp newly :id))
#(when-let [node (js/document.getElementById (str "ann-" (name primary)))] (sort-by (juxt (comp - :lo) :hi))
(.scrollIntoView node #js {:block "center" :behavior "smooth"})))))) 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)) (r/after-render #(position-playhead! fps zoom playhead))
nil)) nil))