fix(studio): make sdk cutover transactional (#2155)

This commit is contained in:
James Russo
2026-07-15 10:29:22 -04:00
committed by GitHub
parent 7d21cc9b8a
commit 42055296ee
45 changed files with 2481 additions and 654 deletions
@@ -9,11 +9,12 @@
* composition, so behavior there is unchanged.
*/
import type { ReactNode } from "react";
import { useCallback, type ReactNode } from "react";
import type { DomEditSelection } from "./editor/domEditingTypes";
import { useSdkSession } from "../hooks/useSdkSession";
import { useVariablesPersist, type UseVariablesPersistParams } from "../hooks/useVariablesPersist";
import { VariablePromoteProvider } from "../contexts/VariablePromoteContext";
import { getStudioSaveErrorMessage } from "../utils/studioSaveDiagnostics";
/** Persist wiring minus the target — this provider derives the target from the selection. */
type PersistDeps = Omit<UseVariablesPersistParams, "sdkSession" | "activeCompPath">;
@@ -22,12 +23,14 @@ export function DesignPanelPromoteProvider({
selection,
projectId,
activeCompPath,
showToast,
children,
...persistDeps
}: PersistDeps & {
selection: DomEditSelection | null;
projectId: string | null;
activeCompPath: string | null;
showToast: (message: string, tone?: "error" | "info") => void;
children: ReactNode;
}) {
const targetPath = selection?.sourceFile || activeCompPath || "index.html";
@@ -35,10 +38,20 @@ export function DesignPanelPromoteProvider({
const persist = useVariablesPersist({
...persistDeps,
sdkSession: handle.session,
publishSdkSession: handle.publish,
activeCompPath: targetPath,
});
const handlePersistError = useCallback(
(error: unknown) => showToast(getStudioSaveErrorMessage(error), "error"),
[showToast],
);
return (
<VariablePromoteProvider session={handle.session} selection={selection} persist={persist}>
<VariablePromoteProvider
session={handle.session}
selection={selection}
persist={persist}
onPersistError={handlePersistError}
>
{children}
</VariablePromoteProvider>
);
@@ -15,7 +15,7 @@ import type { IframeWindow } from "../player/lib/playbackTypes";
import { STUDIO_INSPECTOR_PANELS_ENABLED } from "./editor/manualEditingAvailability";
import type { Composition } from "@hyperframes/sdk";
import type { EditHistoryKind } from "../utils/editHistory";
import { useSlideshowPersist } from "../hooks/useSlideshowPersist";
import { useSlideshowPersist, type UseSlideshowPersistParams } from "../hooks/useSlideshowPersist";
import { DesignPanelPromoteProvider } from "./DesignPanelPromoteProvider";
import { useStudioPlaybackContext, useStudioShellContext } from "../contexts/StudioContext";
@@ -47,6 +47,7 @@ export interface StudioRightPanelProps {
onToggleRecording?: () => void;
/** Dependencies for the Slideshow persist callback, threaded from App.tsx. */
sdkSession: Composition | null;
publishSdkSession: NonNullable<UseSlideshowPersistParams["publishSdkSession"]>;
reloadPreview: () => void;
domEditSaveTimestampRef: MutableRefObject<number>;
recordEdit: (entry: {
@@ -66,6 +67,7 @@ export function StudioRightPanel({
recordingDuration,
onToggleRecording,
sdkSession,
publishSdkSession,
reloadPreview,
domEditSaveTimestampRef,
recordEdit,
@@ -160,6 +162,7 @@ export function StudioRightPanel({
recordEdit,
reloadPreview,
domEditSaveTimestampRef,
publishSdkSession,
});
// Notes path: persists are debounced in SlideshowPanel; coalesceKey ensures
@@ -172,6 +175,7 @@ export function StudioRightPanel({
recordEdit,
reloadPreview,
domEditSaveTimestampRef,
publishSdkSession,
coalesceKey: activeCompPath ? `slideshow-notes:${activeCompPath}` : "slideshow-notes",
});
@@ -305,7 +309,6 @@ export function StudioRightPanel({
},
[projectId, refreshFileTree, showToast],
);
const handleHideAllSelected = () => {
const { elements } = usePlayerStore.getState();
const keys = timelineKeysForSelections(domEditGroupSelections, elements, activeCompPath);
@@ -316,6 +319,7 @@ export function StudioRightPanel({
selection={domEditGroupSelections.length > 1 ? null : domEditSelection}
projectId={projectId}
activeCompPath={activeCompPath}
showToast={showToast}
readProjectFile={readProjectFile}
writeProjectFile={writeProjectFile}
recordEdit={recordEdit}
@@ -507,6 +511,7 @@ export function StudioRightPanel({
) : rightPanelTab === "variables" ? (
<VariablesPanel
sdkSession={sdkSession}
publishSdkSession={publishSdkSession}
reloadPreview={reloadPreview}
domEditSaveTimestampRef={domEditSaveTimestampRef}
recordEdit={recordEdit}
@@ -6,6 +6,7 @@ import type {
VariableValidationIssue,
} from "@hyperframes/sdk";
import type { EditHistoryKind } from "../../utils/editHistory";
import type { PublishSdkSession } from "../../utils/sdkCutover";
import { useStudioPlaybackContext, useStudioShellContext } from "../../contexts/StudioContext";
import { useDomEditContext } from "../../contexts/DomEditContext";
import { useFileManagerContext } from "../../contexts/FileManagerContext";
@@ -32,6 +33,7 @@ function shellSingleQuote(value: string): string {
interface VariablesPanelProps {
sdkSession: Composition | null;
publishSdkSession: PublishSdkSession;
reloadPreview: () => void;
domEditSaveTimestampRef: MutableRefObject<number>;
recordEdit: (entry: {
@@ -247,6 +249,7 @@ const EMPTY_STATE = (
// fallow-ignore-next-line complexity
export const VariablesPanel = memo(function VariablesPanel({
sdkSession,
publishSdkSession,
reloadPreview,
domEditSaveTimestampRef,
recordEdit,
@@ -285,6 +288,7 @@ export const VariablesPanel = memo(function VariablesPanel({
recordEdit,
reloadPreview,
domEditSaveTimestampRef,
publishSdkSession,
});
const declarations = useMemo(