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
13 changes: 6 additions & 7 deletions packages/core/src/runtime/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/producer/src/services/distributed/renderChunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/producer/src/services/render/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/producer/src/services/renderOrchestrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/panels/SlideshowPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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],
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/panels/SlideshowSubPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) ────────────────────────────────────

Expand Down Expand Up @@ -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("");
Expand Down
5 changes: 3 additions & 2 deletions packages/studio/src/components/renders/useRenderQueue.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.",
Expand All @@ -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.`,
Expand Down
2 changes: 1 addition & 1 deletion packages/studio/src/hooks/gsapDragCommit.test.ts
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
5 changes: 3 additions & 2 deletions packages/studio/src/telemetry/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions packages/studio/src/utils/generateId.ts
Original file line number Diff line number Diff line change
@@ -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)}`;
}
4 changes: 3 additions & 1 deletion packages/studio/src/utils/studioTelemetry.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 {
Expand Down
Loading