# How Modly Handles the "Add to Scene" Workflow Node: A Technical Deep Dive

> Learn how Modly handles the Add to Scene workflow node. This technical deep dive explains how Modly validates mesh inputs and pushes geometry to the 3D viewer.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: deep-dive
- Published: 2026-08-20

---

**The "Add to Scene" node is Modly's terminal output node that validates mesh inputs at design-time and pushes generated geometry to the 3D viewer at runtime.**

The `Add to Scene` workflow node serves as the sink for all mesh generation pipelines in the Modly visual editor. Understanding its three-phase lifecycle—label rendering, pre-flight validation, and scene pushing—is essential for building custom workflow nodes that integrate with the 3D viewport.

## Node Type Declaration and Behavior Registry

In [`src/areas/workflows/nodeBehaviors.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/nodeBehaviors.ts), the `BEHAVIORS` lookup table defines how the workflow engine treats each node type.

```typescript
// src/areas/workflows/nodeBehaviors.ts#L25-L28
const BEHAVIORS: Record<string, NodeBehavior> = {
  outputNode: { sceneOutput: true, branchConsumer: true },
  // ...
};

```

The `outputNode` entry carries two critical flags:

- **`sceneOutput: true`** — Signals the runner that this node terminates in a scene push, not a data return
- **`branchConsumer: true`** — Restricts the node to single-branch inputs (Wait-branch pattern), preventing parallel mesh merges

This behavior-driven architecture means any future node can become a scene sink by adding `{ sceneOutput: true }` to `BEHAVIORS` without modifying core engine code.

## UI Label: "Add to Scene" Rendering

The user-facing label is resolved at runtime from [`src/areas/workflows/preflight.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/preflight.ts):

```typescript
// src/areas/workflows/preflight.ts#L13-L18
export function nodeLabel(type: string): string {
  if (type === 'outputNode') return 'Add to Scene';
  // additional label mappings...
}

```

Modly uses this function rather than static labels to support localization and dynamic node naming based on configuration state.

## Pre-Flight Validation of Mesh Inputs

Before execution, `validateWorkflowPreflight` ensures the `Add to Scene` node receives compatible upstream data:

```typescript
// src/areas/workflows/preflight.ts#L42-L46
function getNodeOutputType(node: WorkflowNode): string {
  if (node.type === 'outputNode') return 'mesh';
  // type resolution for other nodes...
}

```

The validator performs two checks:

1. **Type compatibility** — Confirms the incoming edge carries a `mesh` type (from `meshNode`, `forEachNode`, or similar)
2. **Single connection enforcement** — `branchConsumer: true` blocks multiple upstream branches

Failure raises a blocking issue: `"Add to Scene needs an incoming mesh connection"`.

## Runtime Execution and Scene Push

At execution, the workflow runner queries `isSceneOutput(node.type)` against the behavior table. For `outputNode`, this triggers the scene-push path:

1. Mesh data is serialized from the upstream node's output
2. The Electron main process receives the payload via IPC
3. [`src/electron/main/artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/src/electron/main/artifact-registry-service.ts) handles the actual insertion into the 3D viewer

This separation between validation logic (renderer) and scene mutation (main process) maintains Modly's security model.

## Practical Workflow Definition

Below is a complete minimal workflow connecting a mesh file to the scene output.

```typescript
// Create the mesh source node
const meshNode = {
  id: 'mesh-001',
  type: 'meshNode',
  data: {
    source: 'file',
    path: '/assets/props/crate.obj'
  },
  position: { x: 100, y: 100 }
};

// Create the Add to Scene output node
const addToSceneNode = {
  id: 'output-001',
  type: 'outputNode',  // Triggers sceneOutput behavior
  data: {},            // No parameters required
  position: { x: 400, y: 100 }
};

// Define the connecting edge
const meshToSceneEdge = {
  id: 'edge-001',
  source: 'mesh-001',
  target: 'output-001'
};

// Full workflow assembly
const workflow = {
  nodes: [meshNode, addToSceneNode],
  edges: [meshToSceneEdge]
};

```

Validation will confirm that `meshNode` outputs `mesh` and that `outputNode` accepts it. Runtime execution pushes `crate.obj` into the viewer.

## Key Files and Their Roles

| File | Purpose |
|------|---------|
| [`src/areas/workflows/nodeBehaviors.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/nodeBehaviors.ts) | Declares `outputNode` as `sceneOutput: true, branchConsumer: true` |
| [`src/areas/workflows/preflight.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/preflight.ts) | Provides `"Add to Scene"` label and `getNodeOutputType` for mesh validation |
| [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts) | Lists `outputNode` in `NODE_TYPES_WITHOUT_SOURCE` for UI constraint handling |
| [`src/electron/main/artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/src/electron/main/artifact-registry-service.ts) | Performs actual mesh insertion into the 3D viewer at runtime |

## Summary

- **Behavior-driven design** — The `BEHAVIORS` table in [`nodeBehaviors.ts`](https://github.com/lightningpixel/modly/blob/main/nodeBehaviors.ts) makes `outputNode` a scene sink via `sceneOutput: true`
- **Label resolution** — `nodeLabel()` in [`preflight.ts`](https://github.com/lightningpixel/modly/blob/main/preflight.ts) returns `"Add to Scene"` for UI display
- **Strict validation** — Pre-flight guarantees a single mesh input via `getNodeOutputType` and `branchConsumer: true`
- **Secure execution** — Scene mutation is delegated to Electron's main process through the artifact registry service

## Frequently Asked Questions

### What happens if the Add to Scene node has no connected upstream node?

The pre-flight validator raises a blocking issue: `"Add to Scene needs an incoming mesh connection"`. The workflow cannot execute until a `meshNode`, `forEachNode`, or other mesh-producing node is wired to it.

### Can I have multiple Add to Scene nodes in one workflow?

Yes. Each `outputNode` operates independently. The workflow runner processes all nodes where `isSceneOutput()` returns true, pushing each to the scene in execution order. However, `branchConsumer: true` means each `outputNode` can only receive from one branch.

### How do I create a custom node that pushes to the 3D scene?

Add an entry to `BEHAVIORS` in [`nodeBehaviors.ts`](https://github.com/lightningpixel/modly/blob/main/nodeBehaviors.ts) with `sceneOutput: true`, implement `getNodeOutputType` to return `'mesh'`, and ensure your node's runtime handler produces valid geometry data. The runner will automatically route to the scene-push path.

### Why is the actual scene insertion handled in the main Electron process?

Renderer-process isolation prevents untrusted workflow code from directly manipulating the 3D viewport. The `artifact-registry-service` in the main process acts as a gated bridge, validating and sanitizing mesh data before insertion.