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
4 changes: 2 additions & 2 deletions packages/studio/fixtures/storyboard-sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ It exercises the storyboard contract end to end:
- Frame 5 (`05-cta.html`) is intentionally **absent** and `status: outline`, so
the grid has an outline placeholder to render.

Preview the storyboard view (flag-gated):
Preview the storyboard view:

```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:
Expand Down
7 changes: 2 additions & 5 deletions packages/studio/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ import {
import type { DomEditSelection } from "./components/editor/domEditing";
import { StudioHeader } from "./components/StudioHeader";
import { useGestureCommit } from "./hooks/useGestureCommit";
import {
STUDIO_KEYFRAMES_ENABLED,
STUDIO_STORYBOARD_ENABLED,
} from "./components/editor/manualEditingAvailability";
import { STUDIO_KEYFRAMES_ENABLED } from "./components/editor/manualEditingAvailability";
import { GestureTrailOverlay } from "./components/editor/GestureTrailOverlay";
import { StudioLeftSidebar } from "./components/StudioLeftSidebar";
import { StudioPreviewArea } from "./components/StudioPreviewArea";
Expand All @@ -65,7 +62,7 @@ type CanvasRect = { left: number; top: number; width: number; height: number };
export function StudioApp() {
const { projectId, resolving, waitingForServer } = useServerConnection();
const initialUrlStateRef = useRef(readStudioUrlStateFromWindow());
const viewModeValue = useViewModeState(STUDIO_STORYBOARD_ENABLED);
const viewModeValue = useViewModeState();

// sessionStorage-backed: fires once per tab, survives HMR remounts
useEffect(() => {
Expand Down
5 changes: 2 additions & 3 deletions packages/studio/src/components/StudioHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { RotateCcw, RotateCw, Camera } from "../icons/SystemIcons";
import {
STUDIO_INSPECTOR_PANELS_ENABLED,
STUDIO_MANUAL_EDITING_DISABLED_TITLE,
STUDIO_STORYBOARD_ENABLED,
} from "./editor/manualEditingAvailability";
import { getHistoryShortcutLabel } from "../utils/studioHelpers";
import { useStudioShellContext } from "../contexts/StudioContext";
Expand Down Expand Up @@ -204,8 +203,8 @@ export function StudioHeader({
</span>
<span className="text-[11px] font-medium text-neutral-300">{projectId}</span>
</div>
{/* Center: storyboard / preview toggle (flag-gated) */}
{STUDIO_STORYBOARD_ENABLED && <ViewModeToggle />}
{/* Center: storyboard / preview toggle */}
<ViewModeToggle />
{/* Right: toolbar buttons */}
<div className="flex items-center gap-1.5">
<button
Expand Down
10 changes: 0 additions & 10 deletions packages/studio/src/components/editor/manualEditingAvailability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,6 @@ export const STUDIO_RAZOR_TOOL_ENABLED = resolveStudioBooleanEnvFlag(
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;

// Stage 7 Step 3c: SDK cutover — routes inline-style ops through SDK dispatch
Expand Down
29 changes: 10 additions & 19 deletions packages/studio/src/contexts/ViewModeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,11 @@ export interface ViewModeValue {
}

/**
* Owns the view-mode state. When `enabled` is false (storyboard flag off) the
* mode is pinned to `timeline` and the URL is left untouched, so the feature is
* fully inert until the flag is on.
* Owns the view-mode state — initial read from `?view=`, toggling, popstate sync.
* Storyboard mode is always available; no flag gating.
*/
export function useViewModeState(enabled: boolean): ViewModeValue {
const [viewMode, setMode] = useState<StudioViewMode>(() =>
enabled ? readViewModeFromUrl() : "timeline",
);
export function useViewModeState(): ViewModeValue {
const [viewMode, setMode] = useState<StudioViewMode>(() => readViewModeFromUrl());

// Reflect genuine browser back/forward between history entries with a different
// `?view=`. Note: our own writes use `replaceState` (below), which does NOT fire
Expand All @@ -60,23 +57,17 @@ export function useViewModeState(enabled: boolean): ViewModeValue {
// the mount-time read); a scripted `pushState`/`replaceState` to `?view=` would not be
// reflected here, by design.
useEffect(() => {
if (!enabled) return;
const onPopState = () => setMode(readViewModeFromUrl());
window.addEventListener("popstate", onPopState);
return () => window.removeEventListener("popstate", onPopState);
}, [enabled]);
}, []);

const setViewMode = useCallback(
(mode: StudioViewMode) => {
if (!enabled) return;
setMode(mode);
writeViewModeToUrl(mode);
},
[enabled],
);
const setViewMode = useCallback((mode: StudioViewMode) => {
setMode(mode);
writeViewModeToUrl(mode);
}, []);

const effectiveMode = enabled ? viewMode : "timeline";
return useMemo(() => ({ viewMode: effectiveMode, setViewMode }), [effectiveMode, setViewMode]);
return useMemo(() => ({ viewMode, setViewMode }), [viewMode, setViewMode]);
}

const ViewModeContext = createContext<ViewModeValue | null>(null);
Expand Down
2 changes: 1 addition & 1 deletion skills-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"files": 7
},
"hyperframes-core": {
"hash": "6243b6f09c94cce1",
"hash": "57d63243e8dcd11e",
"files": 13
},
"hyperframes-creative": {
Expand Down
2 changes: 1 addition & 1 deletion skills/hyperframes-core/references/storyboard-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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)

Expand Down
Loading