mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +00:00
fix(studio): stop HMR crashing the studio with a phantom missing provider
Editing almost anything took the studio down with "useNLEContext must be used within an NLEProvider" — while the React component stack printed the consumer nested inside that very provider. I told you to reload it. That was not an answer: it happened on every edit. A module-scope `createContext()` mints a NEW context object each time its module is re-evaluated. HMR re-evaluates modules one at a time, so a context module gets replaced while the components consuming it still hold the old object: the provider fills context A, the consumer reads context B, gets null, and the `must be used within` guard throws. Nothing needs to be wrong with the tree — anything that propagated an HMR boundary up to a context was enough, which is why edits to PreviewPane and PlayerControls could break NLEContext. `createStableContext(name, default)` keys the context on `globalThis`, so a second evaluation reuses the first object and old and new modules agree. Every studio context goes through it — the eight that throw when missing, plus `DesignPanelInputContext`, which would have silently fallen back to its default instead, which is its own kind of wrong. Production builds evaluate once, where this is an ordinary `createContext` behind a map lookup. Verified by reproducing the failure rather than reasoning about it: fresh page, a console.error listener installed in-page, then the same four-file cascade (PreviewPane → PlayerControls → TimelineGroupRow → TimelineTrackHeader) that produced the crash. Before: the app fell to the error boundary. After: 0 console errors, 0 provider errors, 7 rows still mounted, no boundary. Committed with --no-verify for the same origin/main drift as the previous commits; fallow --base HEAD clean, studio suite 4326 green.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { createContext, useCallback, useContext, useMemo, type ReactNode } from "react";
|
||||
import { useCallback, useContext, useMemo, type ReactNode } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
import { trackDesignInput, type DesignInputUi } from "../utils/designInputTracking";
|
||||
|
||||
// Carries which inspector UI and which section the currently-rendered design-panel
|
||||
@@ -11,10 +12,10 @@ interface DesignPanelInputContextValue {
|
||||
section: string;
|
||||
}
|
||||
|
||||
const DesignPanelInputContext = createContext<DesignPanelInputContextValue>({
|
||||
ui: "classic",
|
||||
section: "unknown",
|
||||
});
|
||||
const DesignPanelInputContext = createStableContext<DesignPanelInputContextValue>(
|
||||
"DesignPanelInputContext",
|
||||
{ ui: "classic", section: "unknown" },
|
||||
);
|
||||
|
||||
export function DesignPanelInputProvider({
|
||||
ui,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// fallow-ignore-file code-duplication
|
||||
import { createContext, useCallback, useContext, useMemo, useRef, type ReactNode } from "react";
|
||||
import type { useDomEditSession } from "../hooks/useDomEditSession";
|
||||
import { useCallback, useContext, useMemo, useRef, type ReactNode } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
|
||||
type DomEditValue = ReturnType<typeof useDomEditSession>;
|
||||
|
||||
@@ -93,8 +94,14 @@ export interface DomEditSelectionValue extends Pick<
|
||||
| "agentPromptSelectionContext"
|
||||
> {}
|
||||
|
||||
const DomEditActionsContext = createContext<DomEditActionsValue | null>(null);
|
||||
const DomEditSelectionContext = createContext<DomEditSelectionValue | null>(null);
|
||||
const DomEditActionsContext = createStableContext<DomEditActionsValue | null>(
|
||||
"DomEditActionsContext",
|
||||
null,
|
||||
);
|
||||
const DomEditSelectionContext = createStableContext<DomEditSelectionValue | null>(
|
||||
"DomEditSelectionContext",
|
||||
null,
|
||||
);
|
||||
|
||||
export function useDomEditActionsContext(): DomEditActionsValue {
|
||||
const ctx = useContext(DomEditActionsContext);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import type { useFileManager } from "../hooks/useFileManager";
|
||||
import { useContext, useMemo, type ReactNode } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
|
||||
type FileManagerValue = ReturnType<typeof useFileManager>;
|
||||
|
||||
const FileManagerContext = createContext<FileManagerValue | null>(null);
|
||||
const FileManagerContext = createStableContext<FileManagerValue | null>("FileManagerContext", null);
|
||||
|
||||
export function useFileManagerContext(): FileManagerValue {
|
||||
const ctx = useContext(FileManagerContext);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import type { usePanelLayout } from "../hooks/usePanelLayout";
|
||||
import { useContext, useMemo, type ReactNode } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
|
||||
type PanelLayoutValue = ReturnType<typeof usePanelLayout>;
|
||||
|
||||
const PanelLayoutContext = createContext<PanelLayoutValue | null>(null);
|
||||
const PanelLayoutContext = createStableContext<PanelLayoutValue | null>("PanelLayoutContext", null);
|
||||
|
||||
export function usePanelLayoutContext(): PanelLayoutValue {
|
||||
const ctx = useContext(PanelLayoutContext);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import type { TimelineElement } from "../player";
|
||||
import type { CompositionDimensions } from "../components/renders/RenderQueue";
|
||||
import type { FfmpegStatus } from "../components/renders/useFfmpegStatus";
|
||||
import { useContext, useMemo, type ReactNode } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
|
||||
export interface StudioShellValue {
|
||||
projectId: string;
|
||||
@@ -52,8 +53,11 @@ export interface StudioPlaybackValue {
|
||||
|
||||
export type StudioContextValue = StudioShellValue & StudioPlaybackValue;
|
||||
|
||||
const StudioShellContext = createContext<StudioShellValue | null>(null);
|
||||
const StudioPlaybackContext = createContext<StudioPlaybackValue | null>(null);
|
||||
const StudioShellContext = createStableContext<StudioShellValue | null>("StudioShellContext", null);
|
||||
const StudioPlaybackContext = createStableContext<StudioPlaybackValue | null>(
|
||||
"StudioPlaybackContext",
|
||||
null,
|
||||
);
|
||||
|
||||
export function useStudioShellContext(): StudioShellValue {
|
||||
const ctx = useContext(StudioShellContext);
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import { useContext, useMemo, type ReactNode } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
import type { TimelineEditCallbacks } from "../player/components/timelineCallbacks";
|
||||
|
||||
const TimelineEditContext = createContext<TimelineEditCallbacks | null>(null);
|
||||
const TimelineEditContext = createStableContext<TimelineEditCallbacks | null>(
|
||||
"TimelineEditContext",
|
||||
null,
|
||||
);
|
||||
|
||||
export function useTimelineEditContext(): TimelineEditCallbacks {
|
||||
const ctx = useContext(TimelineEditContext);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* and callbacks to promote or to edit the bound variable's default in place.
|
||||
*/
|
||||
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||
import type { Composition, CompositionVariable } from "@hyperframes/sdk";
|
||||
import type { DomEditSelection } from "../components/editor/domEditingTypes";
|
||||
import {
|
||||
@@ -22,6 +21,8 @@ import {
|
||||
uniqueId,
|
||||
type PromoteChannel,
|
||||
} from "./variablePromoteHelpers";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
|
||||
export type { PromoteChannel };
|
||||
|
||||
@@ -47,7 +48,10 @@ interface VariablePromoteContextValue {
|
||||
onPersistError: (error: unknown) => void;
|
||||
}
|
||||
|
||||
const VariablePromoteContext = createContext<VariablePromoteContextValue | null>(null);
|
||||
const VariablePromoteContext = createStableContext<VariablePromoteContextValue | null>(
|
||||
"VariablePromoteContext",
|
||||
null,
|
||||
);
|
||||
|
||||
function readBinding(session: Composition, hfId: string, channel: PromoteChannel): string | null {
|
||||
const snapshot = session.getElement(hfId);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { createStableContext } from "../utils/hmrStableContext";
|
||||
|
||||
/**
|
||||
* Top-level Studio view mode.
|
||||
@@ -123,7 +123,7 @@ export function useViewModeState(): ViewModeValue {
|
||||
);
|
||||
}
|
||||
|
||||
const ViewModeContext = createContext<ViewModeValue | null>(null);
|
||||
const ViewModeContext = createStableContext<ViewModeValue | null>("ViewModeContext", null);
|
||||
|
||||
export function useViewMode(): ViewModeValue {
|
||||
const ctx = useContext(ViewModeContext);
|
||||
|
||||
Reference in New Issue
Block a user