story-kit: merge 137_bug_lozengeflycontext_animation_queue_race_condition_on_rapid_updates

This commit is contained in:
Dave
2026-02-24 13:09:25 +00:00
parent 92a75215f0
commit 17abf36d9f
2 changed files with 314 additions and 12 deletions

View File

@@ -101,6 +101,11 @@ export function LozengeFlyProvider({
const pendingFlyInActionsRef = useRef<PendingFlyIn[]>([]);
const pendingFlyOutActionsRef = useRef<PendingFlyOut[]>([]);
// Track the active animation ID per story/agent so stale timeouts
// from superseded animations don't prematurely clear state.
const activeFlyInPerStory = useRef<Map<string, string>>(new Map());
const activeFlyOutPerAgent = useRef<Map<string, string>>(new Map());
const [pendingFlyIns, setPendingFlyIns] = useState<ReadonlySet<string>>(
new Set(),
);
@@ -258,6 +263,7 @@ export function LozengeFlyProvider({
const rosterRect = rosterEl.getBoundingClientRect();
const id = `fly-in-${action.agentName}-${action.storyId}-${Date.now()}`;
activeFlyInPerStory.current.set(action.storyId, id);
setFlyingLozenges((prev) => [
...prev,
@@ -282,14 +288,19 @@ export function LozengeFlyProvider({
});
});
// After the transition completes, remove clone and reveal slot lozenge
// After the transition completes, remove clone and reveal slot lozenge.
// Only clear pendingFlyIns if this is still the active animation for
// this story — a newer animation may have superseded this one.
setTimeout(() => {
setFlyingLozenges((prev) => prev.filter((l) => l.id !== id));
setPendingFlyIns((prev) => {
const next = new Set(prev);
next.delete(action.storyId);
return next;
});
if (activeFlyInPerStory.current.get(action.storyId) === id) {
activeFlyInPerStory.current.delete(action.storyId);
setPendingFlyIns((prev) => {
const next = new Set(prev);
next.delete(action.storyId);
return next;
});
}
}, 500);
}
@@ -307,6 +318,7 @@ export function LozengeFlyProvider({
const rosterRect = rosterEl?.getBoundingClientRect();
const id = `fly-out-${action.agentName}-${action.storyId}-${Date.now()}`;
activeFlyOutPerAgent.current.set(action.agentName, id);
setFlyingLozenges((prev) => [
...prev,
@@ -330,14 +342,18 @@ export function LozengeFlyProvider({
});
});
// Only reveal the roster badge if this is still the active fly-out
// for this agent — a newer fly-out may have superseded this one.
setTimeout(() => {
setFlyingLozenges((prev) => prev.filter((l) => l.id !== id));
// Reveal the roster badge now that the clone has landed.
setFlyingOutAgents((prev) => {
const next = new Set(prev);
next.delete(action.agentName);
return next;
});
if (activeFlyOutPerAgent.current.get(action.agentName) === id) {
activeFlyOutPerAgent.current.delete(action.agentName);
setFlyingOutAgents((prev) => {
const next = new Set(prev);
next.delete(action.agentName);
return next;
});
}
}, 500);
}
}, [pipeline]);