From 0ce8e25a8ab227198c3d561d435532a5948dabbd Mon Sep 17 00:00:00 2001 From: Miguel Angel Simon Sierra Date: Fri, 26 Jun 2026 22:23:29 -0400 Subject: [PATCH] chore(studio): remove debug logging --- .../studio/src/components/TimelineToolbar.tsx | 8 ++--- .../editor/GestureRecordControl.tsx | 4 +-- .../src/components/editor/domEditingGroups.ts | 19 +++++----- packages/studio/src/hooks/useAppHotkeys.ts | 20 +++++++++-- .../studio/src/hooks/useDomEditSession.ts | 3 ++ .../src/hooks/useGsapSelectionHandlers.ts | 4 +++ .../studio/src/hooks/usePreviewInteraction.ts | 36 +++++++++++++++++-- 7 files changed, 75 insertions(+), 19 deletions(-) diff --git a/packages/studio/src/components/TimelineToolbar.tsx b/packages/studio/src/components/TimelineToolbar.tsx index fd9d9ac134..63af38fa52 100644 --- a/packages/studio/src/components/TimelineToolbar.tsx +++ b/packages/studio/src/components/TimelineToolbar.tsx @@ -133,12 +133,12 @@ export function TimelineToolbar({ ); diff --git a/packages/studio/src/components/editor/domEditingGroups.ts b/packages/studio/src/components/editor/domEditingGroups.ts index 5dc0a4f19a..8b90f84c42 100644 --- a/packages/studio/src/components/editor/domEditingGroups.ts +++ b/packages/studio/src/components/editor/domEditingGroups.ts @@ -27,12 +27,15 @@ export function resolveGroupCapture( for (let n: HTMLElement | null = startEl; n; n = n.parentElement) { if (n.hasAttribute("data-hf-group")) groups.push(n); } - if (!activeGroupElement) { - const outermost = groups[groups.length - 1]; - return outermost ? { kind: "unit", element: outermost } : { kind: "child" }; - } - const idx = groups.indexOf(activeGroupElement); - if (idx === -1) return { kind: "out-of-scope" }; - const nestedInside = groups[idx - 1]; - return nestedInside ? { kind: "unit", element: nestedInside } : { kind: "child" }; + const result = ((): GroupCapture => { + if (!activeGroupElement) { + const outermost = groups[groups.length - 1]; + return outermost ? { kind: "unit", element: outermost } : { kind: "child" }; + } + const idx = groups.indexOf(activeGroupElement); + if (idx === -1) return { kind: "out-of-scope" }; + const nestedInside = groups[idx - 1]; + return nestedInside ? { kind: "unit", element: nestedInside } : { kind: "child" }; + })(); + return result; } diff --git a/packages/studio/src/hooks/useAppHotkeys.ts b/packages/studio/src/hooks/useAppHotkeys.ts index 44ef0ec1d2..2748bbbfce 100644 --- a/packages/studio/src/hooks/useAppHotkeys.ts +++ b/packages/studio/src/hooks/useAppHotkeys.ts @@ -8,6 +8,7 @@ import { shouldHandleTimelineToggleHotkey, isEditableTarget } from "../utils/tim import { shouldIgnoreHistoryShortcut } from "../utils/studioHelpers"; import { canSplitElement } from "../utils/timelineElementSplit"; import { STUDIO_RAZOR_TOOL_ENABLED } from "../components/editor/manualEditingAvailability"; +import { trackStudioEvent } from "../utils/studioTelemetry"; function iframeContentWindow(iframe: HTMLIFrameElement | null): Window | null { try { @@ -158,19 +159,27 @@ function dispatchModifierKey(event: KeyboardEvent, key: string, cb: HotkeyCallba !shouldIgnoreHistoryShortcut(event.target) && handleUndoRedoKey( event, - () => void cb.handleUndo(), - () => void cb.handleRedo(), + () => { + trackStudioEvent("keyboard_shortcut", { action: "undo" }); + void cb.handleUndo(); + }, + () => { + trackStudioEvent("keyboard_shortcut", { action: "redo" }); + void cb.handleRedo(); + }, ) ) return true; if (event.key === "1") { event.preventDefault(); + trackStudioEvent("keyboard_shortcut", { action: "tab_compositions" }); cb.leftSidebarRef.current?.selectTab("compositions"); return true; } if (event.key === "2") { event.preventDefault(); + trackStudioEvent("keyboard_shortcut", { action: "tab_assets" }); cb.leftSidebarRef.current?.selectTab("assets"); return true; } @@ -184,17 +193,22 @@ function dispatchModifierKey(event: KeyboardEvent, key: string, cb: HotkeyCallba if (!event.shiftKey && !event.altKey && !isEditableTarget(event.target)) { if (key === "c") { - if (cb.handleCopy()) event.preventDefault(); + if (cb.handleCopy()) { + event.preventDefault(); + trackStudioEvent("keyboard_shortcut", { action: "copy" }); + } return true; } if (key === "v") { event.preventDefault(); + trackStudioEvent("keyboard_shortcut", { action: "paste" }); void cb.handlePaste(); return true; } if (key === "x") { if (usePlayerStore.getState().selectedElementId || cb.domEditSelectionRef.current) { event.preventDefault(); + trackStudioEvent("keyboard_shortcut", { action: "cut" }); void cb.handleCut(); } return true; diff --git a/packages/studio/src/hooks/useDomEditSession.ts b/packages/studio/src/hooks/useDomEditSession.ts index e749c0a7fa..fbdba55ce9 100644 --- a/packages/studio/src/hooks/useDomEditSession.ts +++ b/packages/studio/src/hooks/useDomEditSession.ts @@ -1,4 +1,5 @@ import { useCallback } from "react"; +import { trackStudioEvent } from "../utils/studioTelemetry"; import type { TimelineElement } from "../player"; import type { ImportedFontAsset } from "../components/editor/fontAssets"; import type { EditHistoryKind } from "../utils/editHistory"; @@ -305,6 +306,7 @@ export function useDomEditSession({ showToast("Select at least 2 elements to group", "info"); return; } + trackStudioEvent("group", { action: "create", count: members.length }); void groupSelection(members); }, [domEditGroupSelectionsRef, domEditSelectionRef, groupSelection, showToast]); @@ -315,6 +317,7 @@ export function useDomEditSession({ return; } // Dissolving the group exits any drill-in (the wrapper is about to vanish). + trackStudioEvent("group", { action: "ungroup" }); setActiveGroupElement(null); void ungroupSelection(sel); }, [domEditSelectionRef, ungroupSelection, setActiveGroupElement, showToast]); diff --git a/packages/studio/src/hooks/useGsapSelectionHandlers.ts b/packages/studio/src/hooks/useGsapSelectionHandlers.ts index d3d795e37b..2ac7698f71 100644 --- a/packages/studio/src/hooks/useGsapSelectionHandlers.ts +++ b/packages/studio/src/hooks/useGsapSelectionHandlers.ts @@ -2,6 +2,7 @@ import { useCallback, useRef } from "react"; import type { DomEditSelection } from "../components/editor/domEditing"; import { usePlayerStore } from "../player"; import { trackStudioSaveFailure } from "../utils/studioSaveDiagnostics"; +import { trackStudioEvent } from "../utils/studioTelemetry"; /** * Thin useCallback wrappers that guard on `domEditSelection` before @@ -136,6 +137,7 @@ export function useGsapSelectionHandlers({ (targetSelector: string) => { const sel = domEditSelection ?? lastSelectionRef.current; if (!sel) return; + trackStudioEvent("keyframe", { action: "delete_all" }); deleteAllForSelector(sel, targetSelector); }, [domEditSelection, deleteAllForSelector], @@ -206,6 +208,7 @@ export function useGsapSelectionHandlers({ ) => { const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current; if (!sel) return; + trackStudioEvent("keyframe", { action: "add", property }); addKeyframe(sel, animId, percentage, property, value); }, [domEditSelection, addKeyframe], @@ -224,6 +227,7 @@ export function useGsapSelectionHandlers({ (animId: string, percentage: number, selectionOverride?: DomEditSelection | null) => { const sel = selectionOverride ?? domEditSelection ?? lastSelectionRef.current; if (!sel) return; + trackStudioEvent("keyframe", { action: "remove" }); removeKeyframe(sel, animId, percentage); }, [domEditSelection, removeKeyframe], diff --git a/packages/studio/src/hooks/usePreviewInteraction.ts b/packages/studio/src/hooks/usePreviewInteraction.ts index 148b8207dc..b11b6b80a8 100644 --- a/packages/studio/src/hooks/usePreviewInteraction.ts +++ b/packages/studio/src/hooks/usePreviewInteraction.ts @@ -3,6 +3,7 @@ import { liveTime, usePlayerStore } from "../player"; import { pauseStudioPreviewPlayback } from "../utils/studioPreviewHelpers"; import { STUDIO_PREVIEW_SELECTION_ENABLED } from "../components/editor/manualEditingAvailability"; import { type DomEditSelection } from "../components/editor/domEditing"; +import { trackStudioEvent } from "../utils/studioTelemetry"; // ── Types ── @@ -47,6 +48,12 @@ interface ClickCycleState { const CYCLE_RADIUS_PX = 6; const CYCLE_WINDOW_MS = 600; +// Manual double-click window. `e.detail` can't be trusted here: the first click +// selects the group and re-renders the overlay, so the second click lands on a +// fresh element and the browser's native click-counter resets to 1 — drill-in +// (which keyed off `e.detail >= 2`) never fired. We track time+position instead. +const DOUBLE_CLICK_MS = 400; +const DOUBLE_CLICK_RADIUS_PX = 6; // ── Hook ── @@ -63,21 +70,34 @@ export function usePreviewInteraction({ onClickToSource, }: UsePreviewInteractionParams) { const cycleRef = useRef(null); + const lastDownRef = useRef<{ t: number; x: number; y: number } | null>(null); const handlePreviewCanvasMouseDown = useCallback( // fallow-ignore-next-line complexity async (e: React.MouseEvent, options?: { preferClipAncestor?: boolean }) => { if (!STUDIO_PREVIEW_SELECTION_ENABLED || captionEditMode || compositionLoading) return; + // Manual double-click detection (see DOUBLE_CLICK_MS): the first click + // re-renders the overlay so `e.detail` never reaches 2 on the canvas. + const downTs = Date.now(); + const lastDown = lastDownRef.current; + const isDoubleClick = + e.detail >= 2 || + (lastDown != null && + downTs - lastDown.t < DOUBLE_CLICK_MS && + Math.hypot(e.clientX - lastDown.x, e.clientY - lastDown.y) < DOUBLE_CLICK_RADIUS_PX); + lastDownRef.current = { t: downTs, x: e.clientX, y: e.clientY }; + // Double-click a group → drill into it and select the child under the // pointer (resolve with the group as the explicit drill-in scope, since the // activeGroupElement state hasn't re-rendered yet within this handler). - if (e.detail >= 2 && !e.shiftKey) { + if (isDoubleClick && !e.shiftKey) { const hit = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY); if (hit?.element.hasAttribute("data-hf-group")) { e.preventDefault(); e.stopPropagation(); cycleRef.current = null; + trackStudioEvent("group", { action: "drill_in" }); setActiveGroupElement(hit.element); const child = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, { activeGroupElement: hit.element, @@ -121,9 +141,21 @@ export function usePreviewInteraction({ } // Fresh click — resolve topmost element - const nextSelection = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, { + let nextSelection = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, { preferClipAncestor: options?.preferClipAncestor ?? false, }); + // A null result while drilled into a group means the click landed OUTSIDE that + // group (resolveGroupCapture → out-of-scope). Drill-in isn't sticky: exit it and + // re-resolve at the top level so this click selects whatever's there (or the + // group as a unit). Without this, a stale drill-in keeps selecting children and + // the "first click selects the group" expectation breaks. + if (!nextSelection) { + setActiveGroupElement(null); + nextSelection = await resolveDomSelectionFromPreviewPoint(e.clientX, e.clientY, { + preferClipAncestor: options?.preferClipAncestor ?? false, + activeGroupElement: null, + }); + } if (!nextSelection) { cycleRef.current = null; applyDomSelection(null, { revealPanel: false });