initial code (#2)

* feat: initial code port from hyperframes-internal

Port all OSS-ready packages from the internal monorepo:
- @hyperframes/core — shared types, HTML generation, GSAP utilities, runtime
- @hyperframes/cli — CLI for creating, previewing, and rendering compositions
- @hyperframes/engine — framework-agnostic rendering engine (BeginFrame + FFmpeg)
- @hyperframes/producer — video rendering pipeline (Puppeteer + FFmpeg)
- @hyperframes/ui-player — browser-based video player component
- @hyperframes/studio — composition editor (React frontend + Hono backend)

Includes regression test suite with Docker-based test harness.

All HeyGen-internal references, deployment infrastructure, and
proprietary assets have been removed. Package names migrated
from @app/* to @hyperframes/*.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scrub internal codenames and stale references from OSS port

- Replace static.heygen.ai runtime URLs in test fixtures
- Remove internal CDN publish script (publish-hyperframe-runtime.ts)
- Replace sandbox-studio, sandbox-interceptor, __magicEditRuntime
  with neutral names (studio, hyperframe-runtime, __hyperframeRuntime)
- Fix stale Vault API / localhost references in docs
- Remove broken deprecated_studio link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove remaining internal codenames and stale references

- Delete stale producer README.md and PIPELINE.md (referenced nonexistent files)
- Replace "Cerberus" codename with "HyperFrames" in test design reviews
- Replace magic-edit postMessage identifiers with hf-preview/hf-parent
- Rename debug-magic-edit-timeline.ts to debug-timeline.ts
- Replace "Motion Cut" with "HyperFrames" in Timeline comments
- Fix studio/CLI references to nonexistent archive package
  (use local data/projects/ dir, stub render proxy)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vance Ingalls
2026-03-21 22:43:56 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 10621e7903
commit 9f8e5ba5a1
401 changed files with 54545 additions and 2 deletions
@@ -0,0 +1,70 @@
import { memo } from "react";
import { FileCode, Image, Film, Music, File } from "../../icons/SystemIcons";
interface FileTreeProps {
files: string[];
activeFile: string | null;
onSelectFile: (path: string) => void;
}
const FILE_ICONS: Record<string, { icon: typeof File; color: string }> = {
html: { icon: FileCode, color: "#3B82F6" },
css: { icon: FileCode, color: "#A855F7" },
js: { icon: FileCode, color: "#F59E0B" },
ts: { icon: FileCode, color: "#3B82F6" },
json: { icon: File, color: "#22C55E" },
png: { icon: Image, color: "#22C55E" },
jpg: { icon: Image, color: "#22C55E" },
svg: { icon: Image, color: "#F97316" },
mp4: { icon: Film, color: "#A855F7" },
mp3: { icon: Music, color: "#F59E0B" },
wav: { icon: Music, color: "#F59E0B" },
};
function getFileIcon(path: string) {
const ext = path.split(".").pop()?.toLowerCase() ?? "";
return FILE_ICONS[ext] ?? { icon: File, color: "#737373" };
}
export const FileTree = memo(function FileTree({ files, activeFile, onSelectFile }: FileTreeProps) {
const sorted = [...files].sort((a, b) => {
// index.html first, then alphabetical
if (a === "index.html") return -1;
if (b === "index.html") return 1;
return a.localeCompare(b);
});
return (
<div className="flex flex-col h-full min-h-0">
<div className="px-2.5 py-1.5 border-b border-neutral-800 flex-shrink-0">
<span className="text-2xs font-medium text-neutral-500 uppercase tracking-caps">Files</span>
</div>
<div className="flex-1 overflow-y-auto py-1">
{sorted.map((path) => {
const { icon: Icon, color } = getFileIcon(path);
const isActive = path === activeFile;
const name = path.split("/").pop() ?? path;
const dir = path.includes("/") ? path.split("/").slice(0, -1).join("/") + "/" : "";
return (
<button
key={path}
onClick={() => onSelectFile(path)}
className={`w-full flex items-center gap-2 px-2.5 py-1 min-h-7 text-left transition-all duration-press text-xs ${
isActive
? "bg-neutral-800/60 text-neutral-200"
: "text-neutral-500 hover:bg-neutral-800/30 hover:text-neutral-300 active:scale-[0.98]"
}`}
>
<Icon size={12} style={{ color }} className="flex-shrink-0" />
<span className="truncate">
{dir && <span className="text-neutral-600">{dir}</span>}
{name}
</span>
</button>
);
})}
</div>
</div>
);
});
@@ -0,0 +1,209 @@
import { memo } from "react";
import { X, MousePointer, Move, Type, Palette, Clock, Eye } from "../../icons/SystemIcons";
import { Button, IconButton } from "../ui";
import type { PickedElement } from "../../hooks/useElementPicker";
interface PropertyPanelProps {
element: PickedElement | null;
isPickMode: boolean;
onEnablePick: () => void;
onDisablePick: () => void;
onClearPick: () => void;
onSetStyle: (prop: string, value: string) => void;
onSetDataAttr: (attr: string, value: string) => void;
onSetText?: (text: string) => void;
}
function PropertyRow({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
return (
<div className="flex items-center gap-2">
<span className="text-2xs text-neutral-600 w-16 flex-shrink-0 text-right">{label}</span>
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-neutral-900 border border-neutral-800 rounded px-1.5 py-0.5 text-2xs text-neutral-200 font-mono outline-none focus:border-neutral-600 min-w-0"
/>
</div>
);
}
function ColorRow({ label, value, onChange }: { label: string; value: string; onChange: (v: string) => void }) {
return (
<div className="flex items-center gap-2">
<span className="text-2xs text-neutral-600 w-16 flex-shrink-0 text-right">{label}</span>
<div className="flex items-center gap-1 flex-1">
<div className="w-5 h-5 rounded border border-neutral-700 flex-shrink-0" style={{ backgroundColor: value }} />
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-neutral-900 border border-neutral-800 rounded px-1.5 py-0.5 text-2xs text-neutral-200 font-mono outline-none focus:border-neutral-600 min-w-0"
/>
</div>
</div>
);
}
function SectionHeader({ icon: Icon, label }: { icon: typeof Move; label: string }) {
return (
<div className="flex items-center gap-1.5 mt-2 mb-1">
<Icon size={10} className="text-neutral-600" />
<span className="text-2xs font-medium text-neutral-500 uppercase tracking-wider">{label}</span>
</div>
);
}
export const PropertyPanel = memo(function PropertyPanel({
element,
isPickMode,
onEnablePick,
onDisablePick,
onClearPick,
onSetStyle,
onSetDataAttr,
onSetText,
}: PropertyPanelProps) {
if (!element) {
return (
<div className="flex flex-col items-center justify-center h-full px-4 text-center">
<MousePointer size={20} className="text-neutral-700 mb-2" />
<p className="text-xs text-neutral-500">Click an element in the preview to inspect it</p>
<Button
variant="secondary"
size="sm"
onClick={isPickMode ? onDisablePick : onEnablePick}
className={`mt-3 ${isPickMode ? "bg-blue-500/20 text-blue-400 border-blue-500/30" : ""}`}
>
{isPickMode ? "Pick mode active..." : "Enable Pick Mode"}
</Button>
</div>
);
}
const s = element.computedStyles;
return (
<div className="flex flex-col h-full min-h-0">
{/* Header */}
<div className="flex items-center justify-between px-3 py-2 border-b border-neutral-800 flex-shrink-0">
<div className="flex items-center gap-1.5 min-w-0">
<span className="text-2xs font-mono text-blue-400 truncate">{element.selector}</span>
</div>
<div className="flex items-center gap-1">
<IconButton
icon={<MousePointer size={11} />}
aria-label={isPickMode ? "Disable pick mode" : "Enable pick mode"}
size="sm"
onClick={isPickMode ? onDisablePick : onEnablePick}
className={isPickMode ? "text-blue-400 bg-blue-500/10" : ""}
/>
<IconButton icon={<X size={11} />} aria-label="Clear selection" size="sm" onClick={onClearPick} />
</div>
</div>
{/* Properties */}
<div className="flex-1 overflow-y-auto px-3 py-2 space-y-1">
{/* Element info */}
<div className="flex items-center gap-2 mb-2">
<span className="text-2xs text-neutral-300 font-medium">{element.label}</span>
<span className="text-2xs text-neutral-600 font-mono">&lt;{element.tagName}&gt;</span>
</div>
{/* Position & Size */}
<SectionHeader icon={Move} label="Position & Size" />
<div className="grid grid-cols-2 gap-1">
<PropertyRow label="X" value={s["left"] ?? "auto"} onChange={(v) => onSetStyle("left", v)} />
<PropertyRow label="Y" value={s["top"] ?? "auto"} onChange={(v) => onSetStyle("top", v)} />
<PropertyRow label="W" value={s["width"] ?? "auto"} onChange={(v) => onSetStyle("width", v)} />
<PropertyRow label="H" value={s["height"] ?? "auto"} onChange={(v) => onSetStyle("height", v)} />
</div>
{/* Typography */}
{(element.tagName === "div" ||
element.tagName === "span" ||
element.tagName === "p" ||
element.tagName === "h1" ||
element.tagName === "h2") && (
<>
<SectionHeader icon={Type} label="Typography" />
<PropertyRow label="Size" value={s["font-size"] ?? ""} onChange={(v) => onSetStyle("font-size", v)} />
<PropertyRow label="Weight" value={s["font-weight"] ?? ""} onChange={(v) => onSetStyle("font-weight", v)} />
<PropertyRow
label="Family"
value={s["font-family"]?.split(",")[0] ?? ""}
onChange={(v) => onSetStyle("font-family", v)}
/>
</>
)}
{/* Colors */}
<SectionHeader icon={Palette} label="Colors" />
<ColorRow label="Color" value={s["color"] ?? "#fff"} onChange={(v) => onSetStyle("color", v)} />
<ColorRow
label="Background"
value={s["background-color"] ?? "transparent"}
onChange={(v) => onSetStyle("background-color", v)}
/>
{/* Appearance */}
<SectionHeader icon={Eye} label="Appearance" />
<PropertyRow label="Opacity" value={s["opacity"] ?? "1"} onChange={(v) => onSetStyle("opacity", v)} />
<PropertyRow
label="Radius"
value={s["border-radius"] ?? "0"}
onChange={(v) => onSetStyle("border-radius", v)}
/>
<PropertyRow label="Z-index" value={s["z-index"] ?? "auto"} onChange={(v) => onSetStyle("z-index", v)} />
<PropertyRow label="Transform" value={s["transform"] ?? "none"} onChange={(v) => onSetStyle("transform", v)} />
{/* Timing */}
{(element.dataAttributes["start"] || element.dataAttributes["duration"]) && (
<>
<SectionHeader icon={Clock} label="Timing" />
{element.dataAttributes["start"] != null && (
<PropertyRow
label="Start"
value={element.dataAttributes["start"]}
onChange={(v) => onSetDataAttr("start", v)}
/>
)}
{element.dataAttributes["duration"] != null && (
<PropertyRow
label="Duration"
value={element.dataAttributes["duration"]}
onChange={(v) => onSetDataAttr("duration", v)}
/>
)}
{element.dataAttributes["track-index"] != null && (
<PropertyRow
label="Track"
value={element.dataAttributes["track-index"]}
onChange={(v) => onSetDataAttr("track-index", v)}
/>
)}
</>
)}
{/* Editable text content */}
{element.textContent && (
<>
<SectionHeader icon={Type} label="Text Content" />
<textarea
defaultValue={element.textContent}
onBlur={(e) => onSetText?.(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && e.metaKey) {
(e.target as HTMLTextAreaElement).blur();
}
}}
rows={3}
className="w-full bg-neutral-900 border border-neutral-800 rounded px-2 py-1.5 text-xs text-neutral-200 outline-none focus:border-neutral-600 resize-y leading-relaxed"
placeholder="Edit text..."
/>
</>
)}
</div>
</div>
);
});
@@ -0,0 +1,116 @@
import { useRef, useCallback, memo } from "react";
import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
import { bracketMatching, foldGutter, indentOnInput } from "@codemirror/language";
import { closeBrackets, closeBracketsKeymap } from "@codemirror/autocomplete";
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
import { oneDark } from "@codemirror/theme-one-dark";
import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
import { javascript } from "@codemirror/lang-javascript";
function getLanguageExtension(language: string) {
switch (language) {
case "html":
return html();
case "css":
return css();
case "javascript":
case "js":
case "typescript":
case "ts":
return javascript({ typescript: language === "typescript" || language === "ts" });
default:
return html();
}
}
function detectLanguage(filePath: string): string {
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
const map: Record<string, string> = {
html: "html",
htm: "html",
css: "css",
js: "javascript",
ts: "typescript",
jsx: "javascript",
tsx: "typescript",
json: "javascript",
};
return map[ext] ?? "html";
}
interface SourceEditorProps {
content: string;
filePath?: string;
language?: string;
onChange?: (content: string) => void;
readOnly?: boolean;
}
export const SourceEditor = memo(function SourceEditor({
content,
filePath,
language,
onChange,
readOnly = false,
}: SourceEditorProps) {
const editorRef = useRef<EditorView | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
const mountEditor = useCallback(
(node: HTMLDivElement | null) => {
if (editorRef.current) {
editorRef.current.destroy();
editorRef.current = null;
}
if (!node) return;
containerRef.current = node;
const lang = language ?? (filePath ? detectLanguage(filePath) : "html");
const updateListener = EditorView.updateListener.of((update) => {
if (update.docChanged && onChangeRef.current) {
onChangeRef.current(update.state.doc.toString());
}
});
const state = EditorState.create({
doc: content,
extensions: [
lineNumbers(),
highlightActiveLine(),
highlightActiveLineGutter(),
history(),
foldGutter(),
indentOnInput(),
bracketMatching(),
closeBrackets(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
]),
getLanguageExtension(lang),
oneDark,
updateListener,
EditorState.readOnly.of(readOnly),
EditorView.theme({
"&": { height: "100%" },
".cm-scroller": { overflow: "auto" },
}),
],
});
editorRef.current = new EditorView({ state, parent: node });
},
[content, filePath, language, readOnly],
);
return <div ref={mountEditor} className="h-full w-full overflow-hidden" />;
});