mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio,producer): post-merge regressions and open issues (#1643)
- fix gsapDragCommit.test.ts import for commitGsapPositionFromDrag - add generateId() helper with insecure-context fallback (fixes #1637) - enable STUDIO_KEYFRAMES_ENABLED by default - prevent clock duration from shrinking during playback (fixes #1636) - add maxRetries to rmSync cleanup calls to handle ENOTEMPTY race (fixes #1644)
This commit is contained in:
@@ -2259,15 +2259,14 @@ export function initSandboxRuntimeModular(): void {
|
||||
bindMediaMetadataListeners();
|
||||
}
|
||||
|
||||
// Keep clock duration in sync with the resolved timeline duration.
|
||||
// Catches async timeline rebinds that happen outside the 60-tick
|
||||
// branch (metadata hydration, deferred setTimeout). Note: this reads
|
||||
// the DOM each tick (duration floors query authored windows + the
|
||||
// root's declared data-duration), which also keeps live edits to
|
||||
// data-duration in the studio reflected without a rebind.
|
||||
// Sync clock duration with the resolved timeline each tick (catches async
|
||||
// rebinds, live data-duration edits). Never shrink while playing — transient
|
||||
// short reads cause reachedEnd() → playhead jumps to end (#1636).
|
||||
if (state.capturedTimeline) {
|
||||
const dur = getSafeTimelineDurationSeconds(state.capturedTimeline, 0);
|
||||
if (dur > 0) clock.setDuration(dur);
|
||||
if (dur > 0 && (!clock.isPlaying() || dur >= clock.getDuration())) {
|
||||
clock.setDuration(dur);
|
||||
}
|
||||
}
|
||||
|
||||
// Audio-master clock: three tiers of timing precision.
|
||||
|
||||
@@ -707,7 +707,7 @@ export async function renderChunk(
|
||||
// Clean up only after the hash + perf sidecar landed. Any failure above
|
||||
// leaves the framesDir in place for inspection.
|
||||
try {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
rmSync(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
||||
} catch (err) {
|
||||
log.warn("[renderChunk] failed to remove work dir", {
|
||||
workDir,
|
||||
|
||||
@@ -65,7 +65,7 @@ export async function cleanupRenderResources(input: {
|
||||
// `force: true` swallows ENOENT, so no need to existsSync first.
|
||||
await safeCleanup(
|
||||
`remove workDir (${label})`,
|
||||
() => rmSync(workDir, { recursive: true, force: true }),
|
||||
() => rmSync(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }),
|
||||
log,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1699,7 +1699,7 @@ export async function executeRenderJob(
|
||||
await safeCleanup(
|
||||
"remove workDir",
|
||||
() => {
|
||||
rmSync(workDir, { recursive: true, force: true });
|
||||
rmSync(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
||||
},
|
||||
log,
|
||||
);
|
||||
|
||||
@@ -73,7 +73,7 @@ export const STUDIO_COLOR_GRADING_ENABLED = resolveStudioBooleanEnvFlag(
|
||||
export const STUDIO_KEYFRAMES_ENABLED = resolveStudioBooleanEnvFlag(
|
||||
env,
|
||||
["VITE_STUDIO_ENABLE_KEYFRAMES", "VITE_STUDIO_KEYFRAMES_ENABLED"],
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
export const STUDIO_RAZOR_TOOL_ENABLED = resolveStudioBooleanEnvFlag(
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { SlideshowManifest, SlideHotspot } from "@hyperframes/core/slidesho
|
||||
import { usePlayerStore } from "../../player";
|
||||
import { useDomEditSelectionContext } from "../../contexts/DomEditContext";
|
||||
import { useFileManagerContext } from "../../contexts/FileManagerContext";
|
||||
import { generateId } from "../../utils/generateId";
|
||||
import {
|
||||
SectionHeader,
|
||||
SlideList,
|
||||
@@ -325,7 +326,7 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
|
||||
|
||||
const handleCreateSequence = useCallback(
|
||||
(label: string) => {
|
||||
const id = `seq-${crypto.randomUUID()}`;
|
||||
const id = `seq-${generateId()}`;
|
||||
applyManifest(createSequence(manifestRef.current, id, label)).catch(() => {});
|
||||
},
|
||||
[applyManifest],
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useState, useCallback, useId } from "react";
|
||||
import type { SlideRef, SlideHotspot, SlideSequence } from "@hyperframes/core/slideshow";
|
||||
import type { DomEditSelection } from "../editor/domEditing";
|
||||
import type { SceneInfo } from "./slideshowPanelHelpers";
|
||||
import { generateId } from "../../utils/generateId";
|
||||
|
||||
// ── Section header (accordion toggle) ────────────────────────────────────
|
||||
|
||||
@@ -425,7 +426,7 @@ export function HotspotTool({
|
||||
// fallow-ignore-next-line complexity
|
||||
const handleMakeHotspot = useCallback(() => {
|
||||
if (!selectedSceneId || !targetSequenceId || !elementKey) return;
|
||||
const id = `hotspot-${elementKey}-${crypto.randomUUID()}`;
|
||||
const id = `hotspot-${elementKey}-${generateId()}`;
|
||||
const label = hotspotLabel.trim() || elementKey;
|
||||
onAddHotspot(selectedSceneId, { id, label, target: targetSequenceId });
|
||||
setHotspotLabel("");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
||||
import { trackStudioRenderStart } from "../../telemetry/events";
|
||||
import { getAnonymousId } from "../../telemetry/config";
|
||||
import { generateId } from "../../utils/generateId";
|
||||
|
||||
export interface RenderJob {
|
||||
id: string;
|
||||
@@ -131,7 +132,7 @@ export function useRenderQueue(projectId: string | null) {
|
||||
});
|
||||
} catch {
|
||||
const failedJob: RenderJob = {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateId(),
|
||||
status: "failed",
|
||||
progress: 0,
|
||||
error: "Could not reach render server. Use `hyperframes render` from the CLI instead.",
|
||||
@@ -143,7 +144,7 @@ export function useRenderQueue(projectId: string | null) {
|
||||
}
|
||||
if (!res.ok) {
|
||||
const failedJob: RenderJob = {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateId(),
|
||||
status: "failed",
|
||||
progress: 0,
|
||||
error: `Server error (${res.status}). Check the terminal for details.`,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { describe, expect, it, beforeEach } from "vitest";
|
||||
import type { GsapAnimation } from "@hyperframes/core/gsap-parser";
|
||||
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
||||
import { commitGsapPositionFromDrag } from "./gsapDragPositionCommit";
|
||||
import {
|
||||
commitStaticGsapPosition,
|
||||
commitStaticGsapRotation,
|
||||
parkPlayheadOnKeyframe,
|
||||
type GsapDragCommitCallbacks,
|
||||
} from "./gsapDragCommit";
|
||||
import { commitGsapPositionFromDrag } from "./gsapDragPositionCommit";
|
||||
import { usePlayerStore } from "../player/store/playerStore";
|
||||
|
||||
// Minimal selection whose element has no drag-baseline attributes (origX/Y = 0).
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// localStorage.setItem('hyperframes-studio:telemetryDisabled','1')
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { generateId } from "../utils/generateId";
|
||||
|
||||
const ANON_ID_KEY = "hyperframes-studio:anonymousId";
|
||||
const OPT_OUT_KEY = "hyperframes-studio:telemetryDisabled";
|
||||
const NOTICE_KEY = "hyperframes-studio:telemetryNoticeShown";
|
||||
@@ -18,8 +20,7 @@ function safeLocalStorage(): Storage | null {
|
||||
}
|
||||
|
||||
function newAnonymousId(): string {
|
||||
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
||||
return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
||||
return generateId();
|
||||
}
|
||||
|
||||
export function getAnonymousId(): string {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// ponytail: crypto.randomUUID is undefined on plain HTTP non-localhost origins
|
||||
export function generateId(): string {
|
||||
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { generateId } from "./generateId";
|
||||
|
||||
// PostHog public ingest key — write-only, safe to ship in the client bundle
|
||||
const POSTHOG_API_KEY = "phc_zjjbX0PnWxERXrMHhkEJWj9A9BhGVLRReICgsfTMmpx";
|
||||
const POSTHOG_HOST = "https://us.i.posthog.com";
|
||||
@@ -29,7 +31,7 @@ function getDistinctId(): string {
|
||||
} catch {
|
||||
// localStorage may be unavailable
|
||||
}
|
||||
distinctId = crypto.randomUUID();
|
||||
distinctId = generateId();
|
||||
try {
|
||||
localStorage.setItem("hf-studio-anon-id", distinctId);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user