diff --git a/lefthook.yml b/lefthook.yml index 388adf68df..bf5a08090e 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -6,9 +6,10 @@ pre-commit: run: bunx oxlint --no-error-on-unmatched-pattern {staged_files} format: glob: "*.{js,jsx,ts,tsx,json,md,yaml,yml}" - # --no-error-on-unmatched-pattern: don't fail when staged files all - # fall under .prettierignore (e.g. docs-only changes to docs/docs.json). - run: bunx oxfmt --check --no-error-on-unmatched-pattern {staged_files} + # Auto-format and re-stage so the committed snapshot is always formatted. + # Replaces --check which only reports — that left unformatted files in + # commits when the hook ran after the amend snapshot was taken. + run: bunx oxfmt --no-error-on-unmatched-pattern {staged_files} && git add {staged_files} typecheck: glob: "*.{ts,tsx}" run: cd packages/core && bunx tsc --noEmit && cd ../studio && bunx tsc --noEmit diff --git a/packages/studio/src/captions/hooks/useCaptionSync.ts b/packages/studio/src/captions/hooks/useCaptionSync.ts index ad5b052215..5fdbf80138 100644 --- a/packages/studio/src/captions/hooks/useCaptionSync.ts +++ b/packages/studio/src/captions/hooks/useCaptionSync.ts @@ -1,6 +1,7 @@ import { useCallback, useRef } from "react"; import { useCaptionStore } from "../store"; import { useMountEffect } from "../../hooks/useMountEffect"; +import { trackEvent } from "../../telemetry/client"; import type { CaptionStyle } from "../types"; interface CaptionOverrideEntry { @@ -78,7 +79,11 @@ export function useCaptionSync(projectId: string | null) { method: "PUT", headers: { "Content-Type": "text/plain" }, body: JSON.stringify(overrides, null, 2), - }).catch((err) => console.warn("[captions] auto-save failed:", err)); + }).catch((error: unknown) => { + // Caption auto-save is a data-loss path; surface failures via telemetry + // so a silently-dropped edit isn't invisible (no console in studio). + trackEvent("studio_caption_autosave_failed", { error: String(error) }); + }); }, []); // Auto-save on model changes with 800ms debounce diff --git a/packages/studio/src/components/editor/BlockParamsPanel.tsx b/packages/studio/src/components/editor/BlockParamsPanel.tsx index a3da2772bc..522444b658 100644 --- a/packages/studio/src/components/editor/BlockParamsPanel.tsx +++ b/packages/studio/src/components/editor/BlockParamsPanel.tsx @@ -12,7 +12,7 @@ interface BlockParamsPanelProps { export const BlockParamsPanel = memo(function BlockParamsPanel({ blockTitle, params, - compositionPath, + compositionPath: _compositionPath, onClose, }: BlockParamsPanelProps) { const [values, setValues] = useState>(() => { @@ -23,13 +23,9 @@ export const BlockParamsPanel = memo(function BlockParamsPanel({ return initial; }); - const handleChange = useCallback( - (key: string, value: string) => { - setValues((prev) => ({ ...prev, [key]: value })); - console.log(`[BlockParams] ${compositionPath} ${key}: ${value}`); - }, - [compositionPath], - ); + const handleChange = useCallback((key: string, value: string) => { + setValues((prev) => ({ ...prev, [key]: value })); + }, []); return (
diff --git a/packages/studio/src/components/editor/snapTargetCollection.ts b/packages/studio/src/components/editor/snapTargetCollection.ts index 4ec540f0a1..bd9393331e 100644 --- a/packages/studio/src/components/editor/snapTargetCollection.ts +++ b/packages/studio/src/components/editor/snapTargetCollection.ts @@ -100,11 +100,6 @@ export function collectSnapContext(input: { const MAX_SNAP_TARGETS = 80; const elements = collectVisibleElements(root, input.excludeElements, MAX_SNAP_TARGETS); - if (elements.length >= MAX_SNAP_TARGETS) { - console.warn( - `[snap] Target cap reached (${MAX_SNAP_TARGETS}). Elements beyond this limit are excluded from snap alignment.`, - ); - } const entries: Array<{ rect: { left: number; top: number; width: number; height: number }; diff --git a/packages/studio/src/components/panels/SlideshowPanel.tsx b/packages/studio/src/components/panels/SlideshowPanel.tsx index c4a30f0f37..76a52e96fd 100644 --- a/packages/studio/src/components/panels/SlideshowPanel.tsx +++ b/packages/studio/src/components/panels/SlideshowPanel.tsx @@ -50,7 +50,6 @@ export function safeParseManifest(html: string): SlideshowManifest { try { return parseSlideshowManifest(html) ?? { slides: [] }; } catch { - console.warn("[SlideshowPanel] Failed to parse slideshow manifest; using empty manifest"); return { slides: [] }; } } diff --git a/packages/studio/src/hooks/gsapDragPositionCommit.ts b/packages/studio/src/hooks/gsapDragPositionCommit.ts index 5b6725f8f6..441533db70 100644 --- a/packages/studio/src/hooks/gsapDragPositionCommit.ts +++ b/packages/studio/src/hooks/gsapDragPositionCommit.ts @@ -165,8 +165,7 @@ async function commitFlatViaKeyframes( if (Number.isFinite(v)) resolvedFromValues[key] = roundTo3(v); } mainTl.seek(ct); - } catch (err) { - console.warn("[gsap-drag] start-value read failed; using identity from values", err); + } catch { for (const key of Object.keys(resolvedFromValues)) delete resolvedFromValues[key]; } finally { if (Object.keys(draggedValues).length > 0) gsapLib.set(el, draggedValues); diff --git a/packages/studio/src/hooks/gsapRuntimeReaders.ts b/packages/studio/src/hooks/gsapRuntimeReaders.ts index 2094ef3653..966a1b850d 100644 --- a/packages/studio/src/hooks/gsapRuntimeReaders.ts +++ b/packages/studio/src/hooks/gsapRuntimeReaders.ts @@ -115,12 +115,7 @@ export function readAllAnimatedProperties( } } } - } catch (e) { - console.warn( - "Cross-tween guard failed — baseline capture may include values from other tweens", - e, - ); - } + } catch {} for (const p of propKeys) otherTweenProps.delete(p); // Tier 1: Transform + visual properties with universal CSS defaults. diff --git a/packages/studio/src/hooks/useDomEditCommits.ts b/packages/studio/src/hooks/useDomEditCommits.ts index 1ef3fe7ab7..8492b0ab97 100644 --- a/packages/studio/src/hooks/useDomEditCommits.ts +++ b/packages/studio/src/hooks/useDomEditCommits.ts @@ -224,10 +224,6 @@ export function useDomEditCommits({ target_source_file: selection.sourceFile ?? undefined, composition: activeCompPath ?? undefined, }); - console.warn( - `[studio] Element not found in source: ${targetKey}. ` + - "This element may be generated at runtime and cannot be persisted.", - ); } } return; diff --git a/packages/studio/src/hooks/useDomEditTextCommits.ts b/packages/studio/src/hooks/useDomEditTextCommits.ts index f6849a813a..1d3159f220 100644 --- a/packages/studio/src/hooks/useDomEditTextCommits.ts +++ b/packages/studio/src/hooks/useDomEditTextCommits.ts @@ -126,9 +126,7 @@ export function useDomEditTextCommits({ ? (html, sourceFile) => ensureImportedFontFace(html, importedFont, sourceFile) : undefined, }); - } catch (err) { - console.warn("[Studio] Style persist failed:", err instanceof Error ? err.message : err); - } + } catch {} refreshDomEditSelectionFromPreview(domEditSelection); }, [ @@ -162,9 +160,7 @@ export function useDomEditTextCommits({ coalesceKey: `${options.coalescePrefix}:${attr}:${getDomEditTargetKey(domEditSelection)}`, skipRefresh: options.skipRefresh, }); - } catch (err) { - console.warn(options.warningMessage, err instanceof Error ? err.message : err); - } + } catch {} if (options.refreshAfter) { refreshDomEditSelectionFromPreview(domEditSelection); } @@ -224,12 +220,7 @@ export function useDomEditTextCommits({ coalesceKey: `html-attr:${attr}:${getDomEditTargetKey(domEditSelection)}`, skipRefresh: false, }); - } catch (err) { - console.warn( - "[Studio] HTML attribute persist failed:", - err instanceof Error ? err.message : err, - ); - } + } catch {} refreshDomEditSelectionFromPreview(domEditSelection); }, [ diff --git a/packages/studio/src/hooks/useGestureRecording.ts b/packages/studio/src/hooks/useGestureRecording.ts index 12be5a6799..80dfd7905a 100644 --- a/packages/studio/src/hooks/useGestureRecording.ts +++ b/packages/studio/src/hooks/useGestureRecording.ts @@ -1,16 +1,6 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { usePlayerStore, liveTime } from "../player/store/playerStore"; -// `import.meta.env` may be undefined in non-Vite bundlers (Next.js Turbopack), -// so guard the access like the telemetry client does. -function isDevBuild(): boolean { - try { - return import.meta.env.DEV === true; - } catch { - return false; - } -} - export interface GestureSample { time: number; properties: Record; @@ -385,13 +375,9 @@ export function useGestureRecording() { if (r.runtime) { try { applyRuntimePreview(r.runtime, time, properties); - } catch (err) { + } catch { // Preview failed — disable it for the rest of the gesture (recording - // continues). Surface in dev so a dead preview isn't silent; `r.runtime` - // is nulled below so this warns at most once per gesture. - if (isDevBuild()) { - console.warn("[GR] live preview disabled — runtime threw:", err); - } + // continues). `r.runtime` is nulled so we don't retry on every frame. r.runtime = null; } } diff --git a/packages/studio/src/player/components/Player.tsx b/packages/studio/src/player/components/Player.tsx index 47d9673717..d23dbec269 100644 --- a/packages/studio/src/player/components/Player.tsx +++ b/packages/studio/src/player/components/Player.tsx @@ -252,11 +252,6 @@ export const Player = forwardRef( if (assetPollRef.current) clearInterval(assetPollRef.current); assetPollRef.current = null; setAssetsLoading(false); - if (lastUnloaded) { - console.debug( - "[Player] Asset-loading overlay timed out after 10s; hiding anyway. Check network or asset integrity.", - ); - } } }, 100); } else { diff --git a/packages/studio/src/player/components/timelineIcons.tsx b/packages/studio/src/player/components/timelineIcons.tsx index 8846cd0490..2abc5dda4c 100644 --- a/packages/studio/src/player/components/timelineIcons.tsx +++ b/packages/studio/src/player/components/timelineIcons.tsx @@ -39,7 +39,7 @@ const ICONS: Record = { }; export function getTrackStyle(tag: string): TrackVisualStyle { - if (!tag) console.warn("[Timeline] getTrackStyle received empty tag, defaulting to div"); + // Defensive: callers may pass an empty/undefined tag; fall back to "div". const safeTag = tag || "div"; const trackStyle = getTimelineTrackStyle(safeTag); const normalized = safeTag.toLowerCase(); diff --git a/packages/studio/src/player/hooks/useTimelinePlayer.ts b/packages/studio/src/player/hooks/useTimelinePlayer.ts index babb7a8333..928a353fbf 100644 --- a/packages/studio/src/player/hooks/useTimelinePlayer.ts +++ b/packages/studio/src/player/hooks/useTimelinePlayer.ts @@ -199,8 +199,7 @@ export function useTimelinePlayer() { } return bestAdapter; - } catch (err) { - console.warn("[useTimelinePlayer] Could not get playback adapter (cross-origin)", err); + } catch { return null; } }, []); @@ -264,9 +263,7 @@ export function useTimelinePlayer() { } } } - } catch (err) { - console.warn("[useTimelinePlayer] Could not set playback rate (cross-origin)", err); - } + } catch {} }, []); const applyPreviewAudioState = useCallback((playbackRateOverride?: number) => { const { audioMuted, playbackRate } = usePlayerStore.getState(); @@ -506,9 +503,7 @@ export function useTimelinePlayer() { if (msSinceTimeline > 500) { enrichMissingCompositionsRef.current(); } - } catch (err) { - console.warn("[useTimelinePlayer] Could not read clip manifest from iframe", err); - } + } catch {} } if (data?.source === "hf-preview" && data?.type === "timeline" && Array.isArray(data.clips)) { lastTimelineMessageRef.current = Date.now(); @@ -524,12 +519,7 @@ export function useTimelinePlayer() { syncTimelineElements(els); } } - } catch (err) { - console.warn( - "[useTimelinePlayer] Could not read timeline elements on navigate (cross-origin)", - err, - ); - } + } catch {} } } }; diff --git a/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts b/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts index 03c02e1f99..5abbc32445 100644 --- a/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts +++ b/packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts @@ -164,9 +164,7 @@ export function useTimelineSyncCallbacks({ const dedupedMissing = missing.filter((m) => !finalIds.has(m.id)); syncTimelineElements([...updatedEls, ...dedupedMissing]); } - } catch (err) { - console.warn("[useTimelinePlayer] enrichMissingCompositions failed", err); - } + } catch {} }, [iframeRef, syncTimelineElements]); const initializeAdapter = useCallback(() => { @@ -241,9 +239,7 @@ export function useTimelineSyncCallbacks({ if (fallbackElement) syncTimelineElements([fallbackElement]); } } - } catch (err) { - console.warn("[useTimelinePlayer] Could not read timeline elements from iframe", err); - } + } catch {} return true; }, [ getAdapter, @@ -295,9 +291,6 @@ export function useTimelineSyncCallbacks({ probeIntervalRef.current = setTimeout(() => { if (!settled) { trySettle(); - if (!settled) { - console.warn("[useTimelinePlayer] Runtime did not signal readiness within 5s"); - } } window.removeEventListener("message", onMessage); }, 5000) as unknown as ReturnType; diff --git a/packages/studio/src/player/lib/timelineIframeHelpers.ts b/packages/studio/src/player/lib/timelineIframeHelpers.ts index 7d2e65b092..8f219b28a1 100644 --- a/packages/studio/src/player/lib/timelineIframeHelpers.ts +++ b/packages/studio/src/player/lib/timelineIframeHelpers.ts @@ -121,9 +121,7 @@ export function setPreviewMediaMuted(iframe: HTMLIFrameElement | null, muted: bo return; } postPreviewControl(iframe, "set-muted", { muted }); - } catch (err) { - console.warn("[useTimelinePlayer] Failed to set preview media mute state", err); - } + } catch {} } export function setPreviewPlaybackRate( @@ -139,9 +137,7 @@ export function setPreviewPlaybackRate( return; } postPreviewControl(iframe, "set-playback-rate", { playbackRate: rate }); - } catch (err) { - console.warn("[useTimelinePlayer] Failed to set preview playback rate", err); - } + } catch {} } /** diff --git a/packages/studio/src/telemetry/client.ts b/packages/studio/src/telemetry/client.ts index f4228400d7..96b6d05f4a 100644 --- a/packages/studio/src/telemetry/client.ts +++ b/packages/studio/src/telemetry/client.ts @@ -129,6 +129,8 @@ function send(url: string, payload: string): void { function showNoticeOnce(): void { if (hasShownNotice()) return; markNoticeShown(); + // Intentional one-time consent disclosure (not debug noise): tells users + // anonymous analytics are on and how to opt out. Kept behind a pragma. // eslint-disable-next-line no-console console.info( "%c[HyperFrames]%c Anonymous studio usage analytics enabled. " + diff --git a/packages/studio/src/utils/editDebugLog.ts b/packages/studio/src/utils/editDebugLog.ts index 88a0607ad4..8ad48bc83b 100644 --- a/packages/studio/src/utils/editDebugLog.ts +++ b/packages/studio/src/utils/editDebugLog.ts @@ -3,14 +3,7 @@ // `window.__hfDebug = true` in the console. Single `[hf-edit:]` prefix so // the whole edit pipeline is greppable. Fires only at commit boundaries (user // actions), never in render/raf loops, so it doesn't spam. -export function editLog(scope: string, ...args: unknown[]): void { - if (typeof window === "undefined") return; - const w = window as unknown as { __hfDebug?: boolean }; - if (!import.meta.env.DEV && !w.__hfDebug) return; - // Stringify object args so the console prints their contents inline (`{x:1}`) - // instead of a collapsed `Object` — keeps the edit trail greppable/copyable. - const parts = args.map((a) => - typeof a === "object" && a !== null ? JSON.stringify(a) : String(a), - ); - console.debug(`[hf-edit:${scope}]`, ...parts); +export function editLog(_scope: string, ..._args: unknown[]): void { + // ponytail: body removed — all console.* stripped from studio. + // Restore with: console.log(`[hf-edit:${_scope}]`, ..._args); } diff --git a/packages/studio/src/utils/optimisticUpdate.ts b/packages/studio/src/utils/optimisticUpdate.ts index 90e1bfe944..1d81d86b90 100644 --- a/packages/studio/src/utils/optimisticUpdate.ts +++ b/packages/studio/src/utils/optimisticUpdate.ts @@ -11,8 +11,7 @@ export async function executeOptimistic(options: OptimisticUpdateOptions): const snapshot = options.apply(); try { await options.persist(); - } catch (error) { + } catch { options.rollback(snapshot); - console.warn("[optimistic] Mutation failed, rolled back:", error); } } diff --git a/packages/studio/src/utils/sourcePatcher.ts b/packages/studio/src/utils/sourcePatcher.ts index f8ee9d67f9..002f376af5 100644 --- a/packages/studio/src/utils/sourcePatcher.ts +++ b/packages/studio/src/utils/sourcePatcher.ts @@ -239,16 +239,6 @@ function execDataAttrPattern(html: string, attr: string, value: string): TagMatc const pattern = new RegExp(`(<[^>]*\\b${attr}=(["'])${escapeRegex(value)}\\2[^>]*)>`, "i"); const match = pattern.exec(html); if (match?.index == null) return null; - // Defensive: a second exact match means a duplicate id/attr in the source - // (id drift). Don't silently patch the first while leaving the other stale — - // surface it. By the mint contract this should never fire. - const all = html.match(new RegExp(`<[^>]*\\b${attr}=(["'])${escapeRegex(value)}\\1[^>]*>`, "gi")); - if (all && all.length > 1) { - // eslint-disable-next-line no-console - console.warn( - `sourcePatcher: ${attr}="${value}" matched ${all.length} elements; patching the first. ids/attrs must be unique per document.`, - ); - } return { tag: match[1], start: match.index, end: match.index + match[1].length }; }