mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-04 07:19:52 +00:00
feat(studio): extend SDK shadow to delete/timing/gsap-add + default on (#1473)
* feat(studio): default SDK shadow dispatch on for parity telemetry Shadow mode keeps the server patch path authoritative (no user-visible change) and emits sdk_shadow_dispatch parity signal. Default it on so we collect addressing/serialize-drift telemetry from all traffic before any cutover. Disable via VITE_STUDIO_SDK_SHADOW_ENABLED=false. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): shadow parity for delete/timing/gsap ops + wire delete Extends shadow visibility past the property-edit path. Adds a can()-first shadow core (pure addressing/validity pre-check, works even for GSAP which has no snapshot value) plus runShadowDelete/runShadowTiming/runShadowGsapTween. Parity coverage: delete = getElement null (full); timing = snapshot start/duration/trackIndex (full); gsap = can()+dispatch+returned-id only (animationIds is a stub, tween values are script-level — full fidelity needs serialize() round-trip diffing, out of scope). Wires the delete runner end-to-end via an onElementDeleted callback (useDomEditSession → useDomEditCommits → useElementLifecycleOps), fired after the server delete succeeds. Server stays authoritative. Timing/GSAP wiring follows (each needs threading sdkSession into useTimelineEditing / useGsapScriptCommits). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): wire timing + GSAP-add shadow dispatch Timing: thread sdkSession into useTimelineEditing; fire runShadowTiming after move/resize persist (server authoritative). Moved the useSdkSession call above useTimelineEditing so both share the single session (no duplicate). GSAP: thread sdkSession through useGsapScriptCommits → useGsapAnimationOps; shadow addGsapAnimation via runShadowGsapTween after the server add. Only the add path is shadowed — delete/update key on the server's animationId, which doesn't resolve in the SDK's independent id-space (would emit false cannot_dispatch). "set" has no SDK method, so it's skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): address #1473 review — no-persist shadow session + fallow gate Blocker (Rames): the shadow runners dispatched on the live persisted SDK session, so each shadow op fired the persist queue → an HTTP write of the SDK's serialize() output, clobbering the studio's authoritative write (default-on shipped this). Fix: open the shadow session WITHOUT persist — it reads from the server but never writes back. Shadow dispatches mutate the in-memory model only and are discarded on the next reload-on-change. Cutover (Step 3c+) must re-add persist together with self-write suppression. No persist consumer exists in this stack (cutover is not in main), so this is safe and keeps default-on. Fallow CI gate (Miguel): - drop unused `export` on RecordEditInput (dead-type) - suppress pre-existing CRAP with reasons: commitMutation, addGsapAnimation; file-level complexity on useTimelineEditing (shadow .then() branches nudge several callbacks over threshold — telemetry-only) - suppress 3 pre-existing clones surfaced by adjacent edits (save-error formatter, prop-drilling passthrough, file-change reload handler) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): scrub user content from shadow property-path telemetry Addresses #1473 review concern (Rames): inline-style and text-content edits put user content into the sdk_shadow_dispatch mismatch expected/actual fields. Redact before emit — text-content values fully redacted (length only), others length-capped at 64. The in-memory parity result keeps raw values, so the parity logic and tests are unaffected. 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:
co-authored by
Claude Opus 4.8
parent
2d48369c76
commit
8f15e9f09b
@@ -1,4 +1,11 @@
|
||||
// Pre-existing-complex timeline hook (DOM patch + GSAP position shift/scale +
|
||||
// playback-start resolution); this PR adds guarded shadow-timing dispatches in
|
||||
// the move/resize .then() chains, which nudges several callbacks over the CC
|
||||
// threshold. The added branches are telemetry-only.
|
||||
// fallow-ignore-file complexity
|
||||
import { useCallback, useRef } from "react";
|
||||
import type { Composition } from "@hyperframes/sdk";
|
||||
import { runShadowTiming } from "../utils/sdkShadow";
|
||||
import type { TimelineElement } from "../player";
|
||||
import { usePlayerStore } from "../player";
|
||||
import { useRazorSplit } from "./useRazorSplit";
|
||||
@@ -33,7 +40,7 @@ import type { PersistTimelineEditInput } from "./timelineEditingHelpers";
|
||||
|
||||
// ── Types ──
|
||||
|
||||
export interface RecordEditInput {
|
||||
interface RecordEditInput {
|
||||
label: string;
|
||||
kind: EditHistoryKind;
|
||||
coalesceKey?: string;
|
||||
@@ -53,6 +60,8 @@ interface UseTimelineEditingOptions {
|
||||
pendingTimelineEditPathRef: React.MutableRefObject<Set<string>>;
|
||||
uploadProjectFiles: (files: Iterable<File>, dir?: string) => Promise<string[]>;
|
||||
isRecordingRef?: React.RefObject<boolean>;
|
||||
/** Stage 7 Step 3b: SDK session for shadow timing dispatch (server stays authoritative). */
|
||||
sdkSession?: Composition | null;
|
||||
}
|
||||
|
||||
// ── Hook ──
|
||||
@@ -70,6 +79,7 @@ export function useTimelineEditing({
|
||||
pendingTimelineEditPathRef,
|
||||
uploadProjectFiles,
|
||||
isRecordingRef,
|
||||
sdkSession,
|
||||
}: UseTimelineEditingOptions) {
|
||||
const projectIdRef = useRef(projectId);
|
||||
projectIdRef.current = projectId;
|
||||
@@ -138,6 +148,11 @@ export function useTimelineEditing({
|
||||
value: String(updates.track),
|
||||
});
|
||||
}).then(() => {
|
||||
if (sdkSession)
|
||||
runShadowTiming(sdkSession, element.hfId, {
|
||||
start: updates.start,
|
||||
trackIndex: updates.track,
|
||||
});
|
||||
const pid = projectIdRef.current;
|
||||
if (delta !== 0 && element.domId && pid) {
|
||||
return shiftGsapPositions(pid, filePath, element.domId, delta)
|
||||
@@ -146,7 +161,7 @@ export function useTimelineEditing({
|
||||
}
|
||||
});
|
||||
},
|
||||
[previewIframeRef, enqueueEdit, activeCompPath, reloadPreview],
|
||||
[previewIframeRef, enqueueEdit, activeCompPath, reloadPreview, sdkSession],
|
||||
);
|
||||
|
||||
const handleTimelineElementResize = useCallback(
|
||||
@@ -190,6 +205,11 @@ export function useTimelineEditing({
|
||||
}
|
||||
return patched;
|
||||
}).then(() => {
|
||||
if (sdkSession)
|
||||
runShadowTiming(sdkSession, element.hfId, {
|
||||
start: updates.start,
|
||||
duration: updates.duration,
|
||||
});
|
||||
const pid = projectIdRef.current;
|
||||
if (timingChanged && element.domId && pid) {
|
||||
return scaleGsapPositions(
|
||||
@@ -207,7 +227,7 @@ export function useTimelineEditing({
|
||||
return reloadPreview();
|
||||
});
|
||||
},
|
||||
[previewIframeRef, enqueueEdit, activeCompPath, reloadPreview],
|
||||
[previewIframeRef, enqueueEdit, activeCompPath, reloadPreview, sdkSession],
|
||||
);
|
||||
|
||||
const handleTimelineElementDelete = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user