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
@@ -18,8 +18,7 @@ import {
type InlineInputState,
} from "./FileTreeIcons";
// Re-export for FileTree.tsx consumers
export type { TreeNode, ContextMenuState, InlineInputState };
export type { ContextMenuState, InlineInputState };
export { buildTree, sortChildren, isActiveInSubtree } from "./FileTreeIcons";
const SZ_ICON = 14;
@@ -300,6 +299,7 @@ export const TreeFile = memo(function TreeFile({
onContextMenu,
inlineInput,
onDragStart,
lintInfo,
}: {
node: TreeNode;
depth: number;
@@ -308,6 +308,7 @@ export const TreeFile = memo(function TreeFile({
onContextMenu: (e: React.MouseEvent, path: string, isFolder: boolean) => void;
inlineInput: InlineInputState | null;
onDragStart: (e: React.DragEvent, path: string) => void;
lintInfo?: { count: number; messages: string[] };
}) {
const isActive = node.fullPath === activeFile;
const isRenaming = inlineInput?.mode === "rename" && inlineInput.originalPath === node.fullPath;
@@ -345,7 +346,15 @@ export const TreeFile = memo(function TreeFile({
style={{ paddingLeft: `${8 + depth * 12 + 14}px` }}
>
<FileIcon path={node.name} />
<span className="truncate">{node.name}</span>
<span className="truncate flex-1">{node.name}</span>
{lintInfo && lintInfo.count > 0 && (
<span
className="flex-shrink-0 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[8px] font-bold text-amber-400 text-center mr-1"
title={lintInfo.messages.join("\n")}
>
{lintInfo.count}
</span>
)}
</button>
);
});
@@ -365,6 +374,7 @@ export const TreeFolder = memo(function TreeFolder({
onDrop,
onDragLeave,
dragOverFolder,
lintFindingsByFile,
}: {
node: TreeNode;
depth: number;
@@ -378,6 +388,7 @@ export const TreeFolder = memo(function TreeFolder({
onDrop: (e: React.DragEvent, folderPath: string) => void;
onDragLeave: () => void;
dragOverFolder: string | null;
lintFindingsByFile?: Map<string, { count: number; messages: string[] }>;
}) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const toggle = useCallback(() => setIsOpen((v) => !v), []);
@@ -459,6 +470,7 @@ export const TreeFolder = memo(function TreeFolder({
onContextMenu={onContextMenu}
inlineInput={inlineInput}
onDragStart={onDragStart}
lintInfo={lintFindingsByFile?.get(child.fullPath)}
/>
) : child.children.size > 0 ? (
<TreeFolder
@@ -475,6 +487,7 @@ export const TreeFolder = memo(function TreeFolder({
onDrop={onDrop}
onDragLeave={onDragLeave}
dragOverFolder={dragOverFolder}
lintFindingsByFile={lintFindingsByFile}
/>
) : (
<TreeFile
@@ -486,6 +499,7 @@ export const TreeFolder = memo(function TreeFolder({
onContextMenu={onContextMenu}
inlineInput={inlineInput}
onDragStart={onDragStart}
lintInfo={lintFindingsByFile?.get(child.fullPath)}
/>
),
)}