Summary: Svelte Flow starter scaffold for one-root loopable scene-graph editors with typed scene schemas and custom nodes.
💡 PATTERNS (Best Practices)
- One-Root Topology: Enforce strict single-entry (
RootNode) to guarantee deterministic execution, linear batching, and loop validation. - Typed Scene Schemas: Use
zodorvalibotfor strict nodedatavalidation; sync Svelte FlowNode<T>generic with the schema to guarantee type-safe graphs. - Reactive State Runes (Svelte 5): Use
$state()for local node UI, but centralized$derived()for global graph execution state (playhead, loop counts). - Custom Node Encapsulation: Decouple node logic from UI; pass strictly typed
datavia Svelte props (let { data }: NodeProps<SceneData>). - Cycle Validation (Schmofi Loop): Implement dynamic topological sorting (e.g., Kahn’s algorithm) to validate explicit loopability back to
RootNodewithout causing infinite call stacks.
🚨 ANTI-PATTERNS (Gotchas / Warnings)
- Multi-Root Bleed: Allowing detached sub-graphs destroys the deterministic timeline compilation required for batch video rendering.
- Mutable Node State: Mutating
node.datadirectly without Svelte Flow’supdateNodeData(). This silently breaks internal reactivity sync. - Untyped Flow Edges: Passing payload data via edges implicitly. Fix: Enforce typed connection handles (e.g.,
<Handle type="target" id="video-in" />). - Deep Reactivity Traps: Deeply nested
$stateinside Svelte Flow’s internal store triggers excessive re-renders. Keep scene schemas flat/normalized. - DOM Polling for Playhead: Using
requestAnimationFrameto query DOM nodes for video status. Bind directly to Svelte/HTML5Media element state instead.
🔧 ARCHITECTURE KERNEL (Svelte + Schema)
// 📦 One-Root Typed Schema Core
import { z } from "zod"
import type { Node } from "@xyflow/svelte"
export const SceneNodeSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal("root"), data: z.object({ fps: z.number(), maxLoops: z.number() }) }),
z.object({
type: z.literal("schmofi_video"),
data: z.object({ src: z.string(), trim: z.tuple([z.number(), z.number()]) }),
}),
])
export type SceneGraphNode = z.infer<typeof SceneNodeSchema>
export type FlowNode = Node<SceneGraphNode["data"], SceneGraphNode["type"]>