fix(studio): shift+click on a group member removes it for good - #4754
Conversation
Removing a member with shift+click shrank the canvas group, but the timeline kept the old set, and the timeline syncs its set back onto the canvas, so the member came back about 70 ms later. A one-element marquee over a live group had the same fault. The canvas now publishes the new set whenever the caller states the whole selection (a shift toggle or a marquee), while a late single primary inside the live set still keeps it.
jrusso1020
left a comment
There was a problem hiding this comment.
Approve at c75cb1f8.
The cause in the body checks out against the code. A shift toggle that shrank a group to one element looked, by size alone, like a late async primary inside the live set, so the mirror kept the old set, and the timeline sync re-selected the removed member. The explicit replaceSet from the caller fixes that without a timer.
Callers checked. I checked every caller of announceTimelineSelection in useDomSelection.ts:
applyDomSelection(l.183) passesisAdditiveSelection. Both shift-add and shift-remove state the full group, so replacing the set is right. WithpreserveGroupplus additive,replaceDomEditGroupSelectionalso returns the full group.applyMarqueeSelection(l.514) always passestrue. The additive branch seeds the current group, and the non-additive branch is the whole marquee, so both are complete.refreshDomEditGroupSelectionsFromPreview(l.430) and the clear paths (l.115, l.187) keep the old behaviour. That is correct for the refresh, which is the late-re-resolve case the preserve rule exists for.
Mutation check. With useDomSelection.ts and domSelectionTimelineMirror.ts reverted to main, the two new cases fail: "shift+click removes a member" and "a marquee catches one member of a live group". The re-announce case that pins the old rule passes on both.
Tests. 1074/1074 pass across src/hooks in studio, and studio tsc --noEmit is clean.
Non-blocking. The new mirror comment lists "timeline sync" among the callers that state the group in full, but no timeline-sync path passes replaceSet. Only the shift toggle and the marquee do. It is worth trimming so the next reader doesn't go looking for it.
— Rames
What
In the Studio preview, shift+click on a member of a group selection now removes it, and it stays removed. Before, the member came back a moment later, so a group could never be trimmed by clicking. The same happened when a marquee caught just one member of a live group.
Why
The timeline keeps the selection set, and it syncs that set back onto the canvas. When shift+click shrank a group to one element, the canvas announced only a new anchor and left the timeline's set at two members. About 70 ms later the sync saw two wanted and one on the canvas, and re-selected both (
apply nextGroup:1, thentimeline-sync wanted:2 had:1, thenmarquee hits:2in thehf-selectdebug log). A move right after that dragged the removed element too.The mirror guessed intent from the group size: a one-element group whose anchor is already in the set was treated as a late async primary and kept the old set. A shift toggle or a marquee that ends at one element looks the same by size.
Related work
Refs #3146, which introduced the set-preserving rule this narrows.
How
The caller now says when its group is the whole new selection.
applyDomSelectionpasses that for a shift toggle, andapplyMarqueeSelectionalways does, soannounceTimelineSelectionpublishes the new set in the same step as the canvas change. There is no race left to lose, and no timer. A plain single selection of an element already inside the live set still keeps the set, as before.Test plan
useDomSelection.test.tsnow reads back the set it last published, the way the timeline store does. Two new cases (shift+click removes a member, one-element marquee over a group) fail on main with the set still holding both ids, and pass here. A third case pins the old rule: re-announcing one member keeps the set; it fails if the mirror always replaces the set.Before
Group of two, then shift+click on Tiny. A second later both are selected again.
After
Same gesture. Tiny is removed and stays removed; shift+click on Headline adds it.