From 28bdac114239ff53c90cc406b0a23ee102bbe37f Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Jul 2026 20:34:23 -0400 Subject: [PATCH] fix: half-open mark ranges + robust active-card scroll - Playhead-in-bar tests (active annotation(s), drawing/note bindings) are now half-open [lo hi) via a shared in-bars? helper. merge-bars ceils the exclusive end up to the next clip's first frame, so the old inclusive <= double-counted the boundary: two annotations lit at once and clip A's drawing bled onto clip B's first frame. - commentary-scroll uses scrollIntoView instead of manual offsetTop math, which broke once cards can nest (a nested card's offsetParent is its parent .ann). Co-Authored-By: Claude Opus 4.8 --- tl/src/tl/subs.cljs | 11 ++++++++--- tl/src/tl/views.cljs | 5 ++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tl/src/tl/subs.cljs b/tl/src/tl/subs.cljs index 8dd535c..3f0bcf2 100644 --- a/tl/src/tl/subs.cljs +++ b/tl/src/tl/subs.cljs @@ -146,13 +146,18 @@ (sort-by str/lower-case) vec))) +;; playhead inside a bar — HALF-OPEN [lo hi), so the boundary frame belongs to the +;; next bar only (no double-highlight, no drawing bleeding onto the next clip). +(defn- in-bars? [bars ph] + (some (fn [[lo hi]] (and (<= lo ph) (< ph hi))) bars)) + ;; the annotation the playhead is currently inside (or the latest one passed) — ;; drives the rolling highlight/scroll in the commentary (rf/reg-sub ::active-annotation :<- [::annotations] :<- [::playhead] (fn [[anns ph] _] - (:id (or (some (fn [a] (when (some (fn [[lo hi]] (<= lo ph hi)) (:bars a)) a)) anns) + (:id (or (some (fn [a] (when (in-bars? (:bars a) ph) a)) anns) (last (filter #(<= (:start %) ph) anns)) (first anns))))) @@ -162,7 +167,7 @@ ::active-annotations :<- [::annotations] :<- [::playhead] (fn [[anns ph] _] - (into #{} (keep (fn [a] (when (some (fn [[lo hi]] (<= lo ph hi)) (:bars a)) + (into #{} (keep (fn [a] (when (in-bars? (:bars a) ph) (:id a))) anns)))) @@ -223,7 +228,7 @@ (defn- gids-at [entries ph] (persistent! (reduce (fn [acc {:keys [gids bars]}] - (if (some (fn [[lo hi]] (<= lo ph hi)) bars) (reduce conj! acc gids) acc)) + (if (in-bars? bars ph) (reduce conj! acc gids) acc)) (transient #{}) entries))) ;; drawings to render over the video now (playhead in a bound mark's span). diff --git a/tl/src/tl/views.cljs b/tl/src/tl/views.cljs index 4408f74..f8eb576 100644 --- a/tl/src/tl/views.cljs +++ b/tl/src/tl/views.cljs @@ -1099,7 +1099,10 @@ (r/after-render (fn [] (when-let [c @el] (when-let [node (and active (.querySelector c (str "#ann-" (name active))))] - (set! (.-scrollTop c) (max 0 (- (.-offsetTop node) (/ (.-clientHeight c) 3)))))))) + ;; scrollIntoView (not manual offsetTop math): a nested card's + ;; offsetParent is its parent .ann (position:relative), so offsetTop + ;; would be measured inside the parent, not the scroll container. + (.scrollIntoView node #js {:block "center" :behavior "smooth"}))))) nil)) (defn commentary []