fix(studio): clear the bare keyframe-cache key when an element loses its keyframes (#1482)

The keyframe cache writes three key variants per element: the source-prefixed
key (sourceFile#id), the index.html fallback (index.html#id), and the bare
element id (id). The clear paths only dropped the prefixed variants, leaving
the bare entry behind.

PropertyPanel reads the bare key and gives it precedence over live data
(cacheEntry?.keyframes ?? gsapKeyframes), so after an element's keyframes are
removed the inspector kept rendering the deleted keyframes. Consumers that fall
back to the bare id (timeline diamonds, preview overlay) saw the same stale
entry.

Add clearKeyframeCacheForElement and clearKeyframeCacheForFile and route the
three clear sites through them so the bare key is dropped alongside the prefixed
ones. Each delete is guarded by has to avoid reallocating the cache map for an
absent key.

Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com>
This commit is contained in:
Carlos Alcaraz Gregor
2026-06-16 00:57:50 -04:00
committed by GitHub
co-authored by Carlos Alcaraz
parent 78cce00c50
commit e812fc8895
3 changed files with 179 additions and 14 deletions
+10 -12
View File
@@ -3,6 +3,10 @@ import type { GsapAnimation, GsapKeyframesData, ParsedGsap } from "@hyperframes/
import type { GsapPercentageKeyframe } from "@hyperframes/core/gsap-parser";
import { usePlayerStore } from "../player/store/playerStore";
import { readRuntimeKeyframes, scanAllRuntimeKeyframes } from "./gsapRuntimeBridge";
import {
clearKeyframeCacheForElement,
clearKeyframeCacheForFile,
} from "./gsapKeyframeCacheHelpers";
import { PROPERTY_DEFAULTS, toAbsoluteTime } from "./gsapShared";
function deduplicateKeyframes(keyframes: GsapPercentageKeyframe[]): GsapPercentageKeyframe[] {
@@ -301,10 +305,7 @@ export function useGsapAnimationsForElement(
if (kf.easeEach) easeEach = kf.easeEach;
}
if (allKeyframes.length === 0) {
const { keyframeCache, setKeyframeCache } = usePlayerStore.getState();
if (keyframeCache.has(`${sourceFile}#${elementId}`)) {
setKeyframeCache(`${sourceFile}#${elementId}`, undefined);
}
clearKeyframeCacheForElement(sourceFile, elementId);
return;
}
const dedupedKeyframes = deduplicateKeyframes(allKeyframes);
@@ -358,14 +359,11 @@ export function usePopulateKeyframeCacheForFile(
const sf = sourceFile;
fetchParsedAnimations(projectId, sf).then((parsed) => {
if (!parsed) return;
const { setKeyframeCache, keyframeCache } = usePlayerStore.getState();
const sfPrefix = `${sf}#`;
const fallbackPrefix = "index.html#";
for (const key of keyframeCache.keys()) {
if (key.startsWith(sfPrefix) || (sf !== "index.html" && key.startsWith(fallbackPrefix))) {
setKeyframeCache(key, undefined);
}
}
const { setKeyframeCache } = usePlayerStore.getState();
// Drop the file's stale entries (including the bare keys consumers read)
// before repopulating, so an element whose keyframes were removed and is
// absent from this scan doesn't keep showing diamonds.
clearKeyframeCacheForFile(sf);
const { elements } = usePlayerStore.getState();
const mergedByElement = new Map<string, GsapKeyframesData>();
for (const anim of parsed.animations) {