返回 html-video
SPIKE-REPORT.md
根目录 / packages / studio-next / SPIKE-REPORT.md
1 # Spike report — hyperframes/studio integration (2026-05-29)
2
3 Verdict: **plan A (rebuild html-video studio on top of hf NLELayout) costs
4 several weeks, not one. Pivot to plan B — keep the current vanilla studio,
5 borrow the small leaf components.**
6
7 ## What we tried
8
9 `packages/studio-next/` is a Vite + React 19 app that imports
10 `@hyperframes/studio` 0.6.52 from npm, then attempts:
11
12 1. Mount `<Player directUrl={...}>` pointing at an existing frame served
13 by the production studio (`http://127.0.0.1:3071/preview/<id>/frame/<nid>`).
14 2. Bind `useElementPicker(iframeRef)` to the player's iframe.
15 3. Toggle pick mode + click an element in the loaded frame.
16
17 ## What works
18
19 - **Vite imports `@hyperframes/studio` cleanly.** No transpiler tweaks needed.
20 - **`SourceEditor` (CodeMirror 6 wrapper for HTML/CSS/JS)** renders and edits
21 out of the box — confirmed in the earlier two-pane spike.
22 - **`hyperframes` runtime auto-injection** is real: hf Player polls the iframe
23 every 200ms, detects `__timelines` (GSAP) or absence and injects an IIFE
24 shim. Our agent-generated frames are valid hosts for this.
25 - **Pure utility exports** (`applyPatch`, `parseStyleString`,
26 `mergeStyleIntoTag`, `findElementBlock`, `EaseCurveEditor`) are pure logic
27 and drop in.
28
29 ## What blocks plan A
30
31 - **`Player.directUrl` is a fallback, not the canonical entry.** Source
32 reads `const src = directUrl || /api/projects/${projectId}/preview`.
33 In practice the player still expects the surrounding hf studio backend:
34 - serves `/api/projects/:id/composition` returning a composition.json
35 - hosts the runtime IIFE on a same-origin path
36 - implements asset / file APIs (`/api/projects/:id/files/...`) that the
37 runtime calls back to fetch nested compositions
38 Pointing `directUrl` at our `/preview/:id/frame/:nodeId` request loads
39 the frame's HTML, but the runtime then tries to call back into a hf
40 backend that doesn't exist, and the picker's postMessage handshake
41 (`{ source: "hf-parent", type: "control", action: "enable-pick-mode" }`)
42 is answered by nothing.
43
44 - **`useElementPicker` only works behind hf Player.** The iframe must be
45 the same one the player has injected its runtime into. Our own iframe
46 can't be substituted.
47
48 - **`NLELayout` reads from hf's Composition + FileManager + TimelineElement
49 model**, which are concepts we don't map onto. Our ContentGraph nodes are
50 N independent full-bleed HTML pages; hf's Composition is one timeline
51 with multiple layers / clips on shared tracks.
52
53 - **The NLE shows a single composition's timeline, not "N storyboard
54 cards"**. We'd have to either:
55 - shoehorn each frame into a separate hf composition (and the NLE
56 becomes a per-frame editor with no inter-frame view), or
57 - flatten our N frames into one hf composition with N sequential layers
58 (and lose the per-frame `data-hv-text` editing model)
59
60 Both choices wreck the v0.8 phase-driven flow.
61
62 ## What we can still take
63
64 | component | status | what we'd use it for |
65 |-----------|--------|---------------------|
66 | `SourceEditor` (CodeMirror 6) | ✅ drop-in | Right-pane "Source" tab next to Frame text |
67 | `EaseCurveEditor` | ✅ drop-in | Future motion-curve picker on per-element edits |
68 | `applyPatch` + `parseStyleString` + `mergeStyleIntoTag` + `findElementBlock` | ✅ drop-in | Programmatic edits to frame HTML (used by an inline element picker we control) |
69 | Their styling tokens (`tailwind-preset.ts`) | ⚠️ would need React anyway | Visual consistency if we go React |
70 | `Player` / `Timeline` / `NLELayout` | ❌ requires hf backend | n/a |
71 | `useElementPicker` | ❌ requires hf runtime contract | n/a |
72
73 ## Recommended path (plan B)
74
75 1. **Production studio stays vanilla JS at port 3071.** No React migration.
76 2. **Right-pane gets a "Source" tab** that wraps `SourceEditor` from
77 `@hyperframes/studio`. Joey can edit the active frame's HTML directly
78 in CodeMirror with HTML/CSS/JS syntax + autocomplete. Save via
79 `PUT /api/projects/:id/frames/:nodeId/raw-html` (already exists).
80 3. **Click-to-edit text overlay stays our hand-rolled one** (commit
81 `e17af19` / `5046fa5`). It already works without postMessage and
82 without hf runtime.
83 4. **`packages/studio-next` becomes a permanent home** for any React-only
84 borrowable hf widget (EaseCurveEditor etc), exposed back into the
85 vanilla studio via an `<iframe>` portal or a tiny standalone bundle.
86 No need to React-migrate the main app.
87 5. **If we ever want NLE-style editing**, the path is to ship our own
88 timeline UI on top of our existing frames[] model — not to reshape our
89 model to fit hf's.
90
91 ## Out of scope for this report
92
93 - Alternatives like Remotion / Motion Canvas / Revideo as backend (we're a
94 meta-aggregator anyway — they're future).
95 - Building a hf-compatible backend so `Player` works as designed. Doable
96 but ~1 week, and the resulting integration still won't surface our
97 5-phase chat or ContentGraph — it's a parallel UI for editing one
98 composition at a time.
99
99 lines MARKDOWN