feat(render): Phase 2 — FlexStory render passthrough + journey template seed

Closes the render boundary so a user's scene list (order, per-scene content,
per-scene duration, theme) actually drives the FlexStory engine — the one gap the
scene-engine mapping found.

- render-svc GetFlexStoryProps (db.go): structured per-scene query that groups
  saved_scene_contents BY scene (the flat GetRenderBindings union collides when
  scenes share keys like "title"), recovers blockId from the scene key
  ("<BlockId>__<n>"), and emits the FlexStory props object
  {scenes:[{blockId,durationSec,props}], accentColor, …}.
- render-svc Claim (internal.go): when the template is Remotion + comp starts with
  "FlexStory", send that object as a single "__flexprops__" binding (no protocol
  struct change).
- node-agent remotionProps (remotion.go): if "__flexprops__" is present, pass it
  to `remotion render --props` verbatim (it's the complete props object).
- scripts/seed_flexstory.py: seeds the CharacterJourney template (7 scenes, theme
  colours, FLEXIBLE) with blockId-encoded scene keys, so the studio's existing
  CopyTemplateGraphAsync copies them into saved_scenes with zero studio-svc change.

Both Go services compile; template is live in the catalog (detail 200, per-aspect
previews). End-to-end render verification needs a live Remotion render node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-23 13:45:04 +03:30
parent 2104dd3c84
commit f8ea9af3b6
11 changed files with 211 additions and 1 deletions
@@ -57,6 +57,15 @@ func npxCmd() string {
// (logoText, accentColor, …) and Value is the user's edited string. Anything the
// user didn't touch falls back to the composition's defaultProps.
func remotionProps(job *Job) (string, error) {
// FlexStory (scene engine) passthrough: the orchestrator already built the full
// props object (scenes:[{blockId,durationSec,props}] + theme colours) as a single
// "__flexprops__" binding. Use it verbatim — it's a complete JSON object, not a
// flat key/value map.
for _, b := range job.Bindings {
if b.Key == "__flexprops__" {
return b.Value, nil
}
}
props := make(map[string]string, len(job.Bindings))
for _, b := range job.Bindings {
if b.Key == "" {