# ADR-001: ujsx Trees as Workflow Template IR ## Status Proposed ## Context Flowgraph needs a way to define workflow templates — reusable sequences of operations with conditional branching and parallel execution. The templates must be: 1. **Declarative** — defining *what* should happen, not *how* 2. **Composable** — nesting sequential, parallel, and conditional flows 3. **Serializable** — store in JSON, transmit over APIs, version in git 4. **Validatable** — check against an operation graph before execution 5. **Renderable to multiple targets** — structural validation (DAG) and runtime execution (reactive) The obvious approach is a custom template format: an array of step objects with type discriminators: ```typescript const template = [ { type: "operation", name: "architect" }, { type: "sequential", steps: [...] }, ]; ``` This works but has limitations: - Custom format requires a custom parser, serializer, and validator - No composition primitives — `sequential` and `parallel` are just types in an array - No host switching — a separate compiler is needed for each target (DAG, execution engine) - No incremental updates — changing a step requires rebuilding the entire structure ## Decision Use ujsx `UNode` trees as the workflow template intermediate representation. Workflow components (`Operation`, `Sequential`, `Parallel`, `Conditional`) are `UComponent` functions that produce `UElement` nodes. The template is rendered to different targets through ujsx `HostConfig` implementations. ```typescript const template = h(Sequential, {}, h(Operation, { name: "architect" }), h(Operation, { name: "reviewer" }), ); ``` ## Rationale 1. **No new format** — ujsx already defines `UNode`, `UElement`, `URoot`, type guards, and serialization. We don't need to design, implement, and maintain a template format. 2. **Composition is structural** — `` and `` compose naturally as parent-child structure in a tree. Array-of-objects requires custom merging logic. 3. **Host target switching** — the same `UNode` tree renders to a graphology DAG (for validation) or a reactive engine (for execution) by swapping the `HostConfig`. No template-specific compiler needed. 4. **Incremental updates** — when the ujsx reconciler is implemented, template changes (add/remove/reorder steps) can be applied incrementally without rebuilding the entire DAG. Array-of-objects requires full diffing and rebuilding. 5. **Reactive props** — `@preact/signals-core` enables signal-driven prop updates. An `Operation` node's `name` could be a `signal`, enabling dynamic workflow modification at runtime. 6. **Serialization for free** — `UNode` trees are plain JSON. `JSON.stringify(template)` works. No custom serializer needed. ## Consequences - **Direct dependency on `@alkdev/ujsx`** — flowgraph imports `h`, `createRoot`, `HostConfig`, `ReactiveRoot`, and type definitions from ujsx. This is a direct dependency, not a peer dependency. - **Function props don't serialize** — `Conditional.test` can be a function `(results) => boolean`, which doesn't survive JSON round-trips. Templates with conditional branches need to provide `test` at render time or use expression strings. - **Template components must follow ujsx component contract** — `(props) => UNode`. This is a minimal contract but it means components are synchronous functions that return a tree. - **The template IS the tree** — there is no separate compilation step between the ujsx tree and the render target. The `HostConfig.render()` call IS the compilation. ## References - ujsx architecture: `@alkdev/ujsx/docs/architecture/README.md` - ujsx HostConfig: `@alkdev/ujsx/docs/architecture/host-config.md` - Workflow templates: [workflow-templates.md](../workflow-templates.md) - Host configs: [host-configs.md](../host-configs.md)