Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion packages/studio/src/captions/hooks/useCaptionSync.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down
12 changes: 4 additions & 8 deletions packages/studio/src/components/editor/BlockParamsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>>(() => {
Expand All @@ -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">
Expand Down
5 changes: 0 additions & 5 deletions packages/studio/src/components/editor/snapTargetCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
1 change: 0 additions & 1 deletion packages/studio/src/components/panels/SlideshowPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [] };
}
}
Expand Down
3 changes: 1 addition & 2 deletions packages/studio/src/hooks/gsapDragPositionCommit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 1 addition & 6 deletions packages/studio/src/hooks/gsapRuntimeReaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 0 additions & 4 deletions packages/studio/src/hooks/useDomEditCommits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 3 additions & 12 deletions packages/studio/src/hooks/useDomEditTextCommits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
[
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
},
[
Expand Down
18 changes: 2 additions & 16 deletions packages/studio/src/hooks/useGestureRecording.ts
Original file line number Diff line number Diff line change
@@ -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>;
Expand Down Expand Up @@ -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;
}
}
Expand Down
5 changes: 0 additions & 5 deletions packages/studio/src/player/components/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/studio/src/player/components/timelineIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 4 additions & 14 deletions packages/studio/src/player/hooks/useTimelinePlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}, []);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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 {}
}
}
};
Expand Down
11 changes: 2 additions & 9 deletions packages/studio/src/player/hooks/useTimelineSyncCallbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>;
Expand Down
8 changes: 2 additions & 6 deletions packages/studio/src/player/lib/timelineIframeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/studio/src/telemetry/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. " +
Expand Down
13 changes: 3 additions & 10 deletions packages/studio/src/utils/editDebugLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
3 changes: 1 addition & 2 deletions packages/studio/src/utils/optimisticUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
10 changes: 0 additions & 10 deletions packages/studio/src/utils/sourcePatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

Expand Down
Loading