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 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-07-02 20:34:23 -04:00
parent 8dd2c9030f
commit 28bdac1142
2 changed files with 12 additions and 4 deletions

View file

@ -146,13 +146,18 @@
(sort-by str/lower-case) (sort-by str/lower-case)
vec))) 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) — ;; the annotation the playhead is currently inside (or the latest one passed) —
;; drives the rolling highlight/scroll in the commentary ;; drives the rolling highlight/scroll in the commentary
(rf/reg-sub (rf/reg-sub
::active-annotation ::active-annotation
:<- [::annotations] :<- [::playhead] :<- [::annotations] :<- [::playhead]
(fn [[anns ph] _] (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)) (last (filter #(<= (:start %) ph) anns))
(first anns))))) (first anns)))))
@ -162,7 +167,7 @@
::active-annotations ::active-annotations
:<- [::annotations] :<- [::playhead] :<- [::annotations] :<- [::playhead]
(fn [[anns ph] _] (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))) (:id a)))
anns)))) anns))))
@ -223,7 +228,7 @@
(defn- gids-at [entries ph] (defn- gids-at [entries ph]
(persistent! (persistent!
(reduce (fn [acc {:keys [gids bars]}] (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))) (transient #{}) entries)))
;; drawings to render over the video now (playhead in a bound mark's span). ;; drawings to render over the video now (playhead in a bound mark's span).

View file

@ -1099,7 +1099,10 @@
(r/after-render (r/after-render
(fn [] (when-let [c @el] (fn [] (when-let [c @el]
(when-let [node (and active (.querySelector c (str "#ann-" (name active))))] (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)) nil))
(defn commentary [] (defn commentary []