chore(studio): remove all console.* calls from studio package (#1691)

* chore(studio): remove all console.* calls from studio package

* chore(studio): address review — remove dead stubs, restore consent notice

- Delete empty if-blocks left after console removal (snapTargetCollection,
  Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording
  dev guard + now-unused isDevBuild) and the stale "surface in dev" comment.
- Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher.
- Restore the one-time telemetry consent disclosure in showNoticeOnce (kept
  behind a pragma — it is a user-facing notice, not debug noise).
- Remove the missed timelineIcons console.warn while preserving the
  `tag || "div"` null-safety fallback.
- Route caption auto-save failures (a data-loss path) through telemetry
  instead of swallowing silently.
- Restore the accidentally-clobbered css-var-fonts output.mp4 fixture.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Miguel Ángel
2026-06-24 17:49:36 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent ae8b94c518
commit adb40321d6
19 changed files with 36 additions and 115 deletions
+4 -3
View File
@@ -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
@@ -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
@@ -12,7 +12,7 @@ interface BlockParamsPanelProps {
export const BlockParamsPanel = memo(function BlockParamsPanel({
blockTitle,
params,
compositionPath,
compositionPath: _compositionPath,
onClose,
}: BlockParamsPanelProps) {
const [values, setValues] = useState<Record<string, string>>(() => {
@@ -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 (
<div className="flex flex-col h-full">
@@ -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 };
@@ -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: [] };
}
}
@@ -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);
@@ -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.
@@ -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;
@@ -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);
},
[
@@ -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<string, number>;
@@ -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;
}
}
@@ -252,11 +252,6 @@ export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
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 {
@@ -39,7 +39,7 @@ const ICONS: Record<string, ReactNode> = {
};
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();
@@ -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 {}
}
}
};
@@ -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<typeof setInterval>;
@@ -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 {}
}
/**
+2
View File
@@ -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. " +
+3 -10
View File
@@ -3,14 +3,7 @@
// `window.__hfDebug = true` in the console. Single `[hf-edit:<scope>]` 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);
}
@@ -11,8 +11,7 @@ export async function executeOptimistic<T>(options: OptimisticUpdateOptions<T>):
const snapshot = options.apply();
try {
await options.persist();
} catch (error) {
} catch {
options.rollback(snapshot);
console.warn("[optimistic] Mutation failed, rolled back:", error);
}
}
@@ -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 };
}