mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(studio): make storyboard view default available (remove FF) (#1794)
* feat(studio): make storyboard view default available (remove FF) Removes STUDIO_STORYBOARD_ENABLED. The storyboard view-mode toggle was gated behind a default-off feature flag (VITE_STUDIO_ENABLE_STORYBOARD) since #1529. With the storyboard experience now ready for broad exposure, drop the gating and make the toggle available unconditionally. Changes: - packages/studio/src/components/editor/manualEditingAvailability.ts: delete the STUDIO_STORYBOARD_ENABLED constant. - packages/studio/src/App.tsx: drop the import + FF arg to useViewModeState(). Hook is now called argument-free. - packages/studio/src/components/StudioHeader.tsx: drop the import + the conditional-render guard on <ViewModeToggle />. The toggle always renders in StudioHeader's center slot. - packages/studio/src/contexts/ViewModeContext.tsx: remove the enabled: boolean parameter from useViewModeState() and simplify. - packages/studio/fixtures/storyboard-sample/README.md: drop the VITE_STUDIO_ENABLE_STORYBOARD=1 prefix from the preview command. The VITE_STUDIO_ENABLE_STORYBOARD / VITE_STUDIO_STORYBOARD_ENABLED env vars become no-ops after this change. Co-Authored-By: Jerrai <noreply@anthropic.com> * docs(skills): drop stale VITE_STUDIO_ENABLE_STORYBOARD reference The Storyboard view is now available by default (the FF removed in this PR); storyboard-format.md no longer points at the dead env var, and skills-manifest is regenerated for the hyperframes-core hash. Closes the Via/Magi review nit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Jerrai <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
812a5d4246
commit
f24a1a9ce7
@@ -13,10 +13,10 @@ It exercises the storyboard contract end to end:
|
|||||||
- Frame 5 (`05-cta.html`) is intentionally **absent** and `status: outline`, so
|
- Frame 5 (`05-cta.html`) is intentionally **absent** and `status: outline`, so
|
||||||
the grid has an outline placeholder to render.
|
the grid has an outline placeholder to render.
|
||||||
|
|
||||||
Preview the storyboard view (flag-gated):
|
Preview the storyboard view:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VITE_STUDIO_ENABLE_STORYBOARD=1 npx hyperframes preview packages/studio/fixtures/storyboard-sample
|
npx hyperframes preview packages/studio/fixtures/storyboard-sample
|
||||||
```
|
```
|
||||||
|
|
||||||
Inspect just the parsed manifest the Studio consumes:
|
Inspect just the parsed manifest the Studio consumes:
|
||||||
|
|||||||
@@ -36,10 +36,7 @@ import {
|
|||||||
import type { DomEditSelection } from "./components/editor/domEditing";
|
import type { DomEditSelection } from "./components/editor/domEditing";
|
||||||
import { StudioHeader } from "./components/StudioHeader";
|
import { StudioHeader } from "./components/StudioHeader";
|
||||||
import { useGestureCommit } from "./hooks/useGestureCommit";
|
import { useGestureCommit } from "./hooks/useGestureCommit";
|
||||||
import {
|
import { STUDIO_KEYFRAMES_ENABLED } from "./components/editor/manualEditingAvailability";
|
||||||
STUDIO_KEYFRAMES_ENABLED,
|
|
||||||
STUDIO_STORYBOARD_ENABLED,
|
|
||||||
} from "./components/editor/manualEditingAvailability";
|
|
||||||
import { GestureTrailOverlay } from "./components/editor/GestureTrailOverlay";
|
import { GestureTrailOverlay } from "./components/editor/GestureTrailOverlay";
|
||||||
import { StudioLeftSidebar } from "./components/StudioLeftSidebar";
|
import { StudioLeftSidebar } from "./components/StudioLeftSidebar";
|
||||||
import { StudioPreviewArea } from "./components/StudioPreviewArea";
|
import { StudioPreviewArea } from "./components/StudioPreviewArea";
|
||||||
@@ -65,7 +62,7 @@ type CanvasRect = { left: number; top: number; width: number; height: number };
|
|||||||
export function StudioApp() {
|
export function StudioApp() {
|
||||||
const { projectId, resolving, waitingForServer } = useServerConnection();
|
const { projectId, resolving, waitingForServer } = useServerConnection();
|
||||||
const initialUrlStateRef = useRef(readStudioUrlStateFromWindow());
|
const initialUrlStateRef = useRef(readStudioUrlStateFromWindow());
|
||||||
const viewModeValue = useViewModeState(STUDIO_STORYBOARD_ENABLED);
|
const viewModeValue = useViewModeState();
|
||||||
|
|
||||||
// sessionStorage-backed: fires once per tab, survives HMR remounts
|
// sessionStorage-backed: fires once per tab, survives HMR remounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { RotateCcw, RotateCw, Camera } from "../icons/SystemIcons";
|
|||||||
import {
|
import {
|
||||||
STUDIO_INSPECTOR_PANELS_ENABLED,
|
STUDIO_INSPECTOR_PANELS_ENABLED,
|
||||||
STUDIO_MANUAL_EDITING_DISABLED_TITLE,
|
STUDIO_MANUAL_EDITING_DISABLED_TITLE,
|
||||||
STUDIO_STORYBOARD_ENABLED,
|
|
||||||
} from "./editor/manualEditingAvailability";
|
} from "./editor/manualEditingAvailability";
|
||||||
import { getHistoryShortcutLabel } from "../utils/studioHelpers";
|
import { getHistoryShortcutLabel } from "../utils/studioHelpers";
|
||||||
import { useStudioShellContext } from "../contexts/StudioContext";
|
import { useStudioShellContext } from "../contexts/StudioContext";
|
||||||
@@ -204,8 +203,8 @@ export function StudioHeader({
|
|||||||
</span>
|
</span>
|
||||||
<span className="text-[11px] font-medium text-neutral-300">{projectId}</span>
|
<span className="text-[11px] font-medium text-neutral-300">{projectId}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Center: storyboard / preview toggle (flag-gated) */}
|
{/* Center: storyboard / preview toggle */}
|
||||||
{STUDIO_STORYBOARD_ENABLED && <ViewModeToggle />}
|
<ViewModeToggle />
|
||||||
{/* Right: toolbar buttons */}
|
{/* Right: toolbar buttons */}
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -82,16 +82,6 @@ export const STUDIO_RAZOR_TOOL_ENABLED = resolveStudioBooleanEnvFlag(
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Storyboard view: a top-level, toggleable view that renders STORYBOARD.md as a
|
|
||||||
// contact sheet of live HTML frame tiles, replacing the timeline/preview stage.
|
|
||||||
// Opt-in / off by default until the experience is ready for broad exposure.
|
|
||||||
// VITE_STUDIO_ENABLE_STORYBOARD=1 npx hyperframes preview
|
|
||||||
export const STUDIO_STORYBOARD_ENABLED = resolveStudioBooleanEnvFlag(
|
|
||||||
env,
|
|
||||||
["VITE_STUDIO_ENABLE_STORYBOARD", "VITE_STUDIO_STORYBOARD_ENABLED"],
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
|
export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
|
||||||
|
|
||||||
// Stage 7 Step 3c: SDK cutover — routes inline-style ops through SDK dispatch
|
// Stage 7 Step 3c: SDK cutover — routes inline-style ops through SDK dispatch
|
||||||
|
|||||||
@@ -44,14 +44,11 @@ export interface ViewModeValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Owns the view-mode state. When `enabled` is false (storyboard flag off) the
|
* Owns the view-mode state — initial read from `?view=`, toggling, popstate sync.
|
||||||
* mode is pinned to `timeline` and the URL is left untouched, so the feature is
|
* Storyboard mode is always available; no flag gating.
|
||||||
* fully inert until the flag is on.
|
|
||||||
*/
|
*/
|
||||||
export function useViewModeState(enabled: boolean): ViewModeValue {
|
export function useViewModeState(): ViewModeValue {
|
||||||
const [viewMode, setMode] = useState<StudioViewMode>(() =>
|
const [viewMode, setMode] = useState<StudioViewMode>(() => readViewModeFromUrl());
|
||||||
enabled ? readViewModeFromUrl() : "timeline",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reflect genuine browser back/forward between history entries with a different
|
// Reflect genuine browser back/forward between history entries with a different
|
||||||
// `?view=`. Note: our own writes use `replaceState` (below), which does NOT fire
|
// `?view=`. Note: our own writes use `replaceState` (below), which does NOT fire
|
||||||
@@ -60,23 +57,17 @@ export function useViewModeState(enabled: boolean): ViewModeValue {
|
|||||||
// the mount-time read); a scripted `pushState`/`replaceState` to `?view=` would not be
|
// the mount-time read); a scripted `pushState`/`replaceState` to `?view=` would not be
|
||||||
// reflected here, by design.
|
// reflected here, by design.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!enabled) return;
|
|
||||||
const onPopState = () => setMode(readViewModeFromUrl());
|
const onPopState = () => setMode(readViewModeFromUrl());
|
||||||
window.addEventListener("popstate", onPopState);
|
window.addEventListener("popstate", onPopState);
|
||||||
return () => window.removeEventListener("popstate", onPopState);
|
return () => window.removeEventListener("popstate", onPopState);
|
||||||
}, [enabled]);
|
}, []);
|
||||||
|
|
||||||
const setViewMode = useCallback(
|
const setViewMode = useCallback((mode: StudioViewMode) => {
|
||||||
(mode: StudioViewMode) => {
|
setMode(mode);
|
||||||
if (!enabled) return;
|
writeViewModeToUrl(mode);
|
||||||
setMode(mode);
|
}, []);
|
||||||
writeViewModeToUrl(mode);
|
|
||||||
},
|
|
||||||
[enabled],
|
|
||||||
);
|
|
||||||
|
|
||||||
const effectiveMode = enabled ? viewMode : "timeline";
|
return useMemo(() => ({ viewMode, setViewMode }), [viewMode, setViewMode]);
|
||||||
return useMemo(() => ({ viewMode: effectiveMode, setViewMode }), [effectiveMode, setViewMode]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ViewModeContext = createContext<ViewModeValue | null>(null);
|
const ViewModeContext = createContext<ViewModeValue | null>(null);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"files": 7
|
"files": 7
|
||||||
},
|
},
|
||||||
"hyperframes-core": {
|
"hyperframes-core": {
|
||||||
"hash": "6243b6f09c94cce1",
|
"hash": "57d63243e8dcd11e",
|
||||||
"files": 13
|
"files": 13
|
||||||
},
|
},
|
||||||
"hyperframes-creative": {
|
"hyperframes-creative": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Defines the storyboard's **base data format** only: the `STORYBOARD.md` file shape and the `StoryboardManifest` it parses into. How a workflow _generates_ a storyboard lives in that workflow; the optional narration/TTS file (`SCRIPT.md`) is a separate concern owned by the TTS step, not here.
|
Defines the storyboard's **base data format** only: the `STORYBOARD.md` file shape and the `StoryboardManifest` it parses into. How a workflow _generates_ a storyboard lives in that workflow; the optional narration/TTS file (`SCRIPT.md`) is a separate concern owned by the TTS step, not here.
|
||||||
|
|
||||||
A storyboard is the **plan layer** for a video — an ordered set of **frames** (key moments) in one markdown file. HyperFrames Studio renders it as a contact sheet (the Storyboard view, behind `VITE_STUDIO_ENABLE_STORYBOARD=1`). Parser: `@hyperframes/core/storyboard` → `StoryboardManifest`; read API: `GET /api/projects/<id>/storyboard`.
|
A storyboard is the **plan layer** for a video — an ordered set of **frames** (key moments) in one markdown file. HyperFrames Studio renders it as a contact sheet (the Storyboard view, available by default in every Studio session). Parser: `@hyperframes/core/storyboard` → `StoryboardManifest`; read API: `GET /api/projects/<id>/storyboard`.
|
||||||
|
|
||||||
## Frontmatter (global direction)
|
## Frontmatter (global direction)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user