fix(studio): keyframe bug fixes — gate delete hooks, fix value corruption, gesture recording (#1314)

- Gate stripStudioEditsFromTarget/bakeVisibilityOnDelete behind a
  stripStudioEdits flag on the delete mutation type so they only fire on
  user-initiated deletes, not on internal delete-then-recreate drags.

- Add bakeVisibilityOnDelete to the remove-all-keyframes handler so
  elements with CSS opacity:0 stay visible after collapsing keyframes.

- Fix integer rounding in readAllAnimatedProperties: use 3-decimal
  precision for visual properties (opacity, scale, rotation) instead of
  Math.round which corrupted mid-fade values to 0.

- Guard VISUAL_BASELINE against cross-tween contamination by querying
  __timelines for properties animated by other tweens on the same element.

- Harden bakeVisibilityOnDelete: reverse-scan keyframes for the last one
  containing opacity, guard against relative values (+=/-=/*=), and add
  Number.isFinite check.

- Fix falsy-zero doubling in drag commit: replace || fallback with
  Number.isFinite so a base GSAP position of 0 is correctly preserved.

- Fix gesture recording sign inversion: remove pointerElementOffset
  subtraction from dx/dy formula and instead apply it once to basePosition
  so the element center tracks the pointer.

- Fix TypeScript build errors in gsapSoftReload.ts (6 double-casts).

- Strip all diagnostic logs from production code.
This commit is contained in:
Miguel Ángel
2026-06-10 18:53:06 -04:00
committed by GitHub
parent 3a72aa528d
commit f0b499b582
38 changed files with 1641 additions and 821 deletions
@@ -7,6 +7,7 @@ interface CompositionsTabProps {
onSelect: (comp: string) => void;
onRenderComposition?: (comp: string) => void;
isRendering?: boolean;
lintFindingsByFile?: Map<string, { count: number; messages: string[] }>;
}
const DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
@@ -111,6 +112,7 @@ function CompCard({
onSelect,
onRender,
isRendering,
lintInfo,
}: {
projectId: string;
comp: string;
@@ -118,6 +120,7 @@ function CompCard({
onSelect: () => void;
onRender?: () => void;
isRendering?: boolean;
lintInfo?: { count: number; messages: string[] };
}) {
const [hovered, setHovered] = useState(false);
const [stageSize, setStageSize] = useState(DEFAULT_PREVIEW_STAGE);
@@ -215,8 +218,16 @@ function CompCard({
tabIndex={-1}
/>
</div>
<div className="min-w-0 flex-1">
<span className="text-[11px] font-medium text-neutral-300 truncate block">{name}</span>
<div
className="min-w-0 flex-1"
title={lintInfo && lintInfo.count > 0 ? lintInfo.messages.join("\n") : undefined}
>
<div className="flex items-center gap-1">
<span className="text-[11px] font-medium text-neutral-300 truncate">{name}</span>
{lintInfo && lintInfo.count > 0 && (
<span className="flex-shrink-0 w-2 h-2 rounded-full bg-amber-400" />
)}
</div>
<span className="text-[9px] text-neutral-600 truncate block">{comp}</span>
</div>
{onRender && (
@@ -262,6 +273,7 @@ export const CompositionsTab = memo(function CompositionsTab({
onSelect,
onRenderComposition,
isRendering,
lintFindingsByFile,
}: CompositionsTabProps) {
if (compositions.length === 0) {
return (
@@ -282,6 +294,7 @@ export const CompositionsTab = memo(function CompositionsTab({
onSelect={() => onSelect(comp)}
onRender={onRenderComposition ? () => onRenderComposition(comp) : undefined}
isRendering={isRendering}
lintInfo={lintFindingsByFile?.get(comp)}
/>
))}
</div>
@@ -54,6 +54,8 @@ interface LeftSidebarProps {
isRendering?: boolean;
onLint?: () => void;
linting?: boolean;
lintFindingCount?: number;
lintFindingsByFile?: Map<string, { count: number; messages: string[] }>;
onToggleCollapse?: () => void;
onAddBlock?: (blockName: string) => void;
onPreviewBlock?: (preview: BlockPreviewInfo | null) => void;
@@ -84,6 +86,8 @@ export const LeftSidebar = memo(
isRendering,
onLint,
linting,
lintFindingCount,
lintFindingsByFile,
onToggleCollapse,
onAddBlock,
onPreviewBlock,
@@ -216,6 +220,7 @@ export const LeftSidebar = memo(
onSelect={onSelectComposition}
onRenderComposition={onRenderComposition}
isRendering={isRendering}
lintFindingsByFile={lintFindingsByFile}
/>
)}
{tab === "assets" && (
@@ -242,6 +247,7 @@ export const LeftSidebar = memo(
onDuplicateFile={onDuplicateFile}
onMoveFile={onMoveFile}
onImportFiles={onImportFiles}
lintFindingsByFile={lintFindingsByFile}
/>
</div>
)}
@@ -279,6 +285,11 @@ export const LeftSidebar = memo(
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" />
</svg>
{linting ? "Linting…" : "Lint"}
{!linting && lintFindingCount != null && lintFindingCount > 0 && (
<span className="ml-1 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[9px] font-bold text-amber-400">
{lintFindingCount}
</span>
)}
</button>
</div>
)}