Browse Source

feat(45-01): add ConditionalSwitch type definitions, default data, executor, and validation

- Add MatchMode type (exact, contains, starts-with, ends-with)
- Add ConditionalSwitchRule interface with id, value, mode, label, isMatched
- Add ConditionalSwitchNodeData interface with incomingText and rules array
- Add conditionalSwitch to NodeType union (after switch)
- Add ConditionalSwitchNodeData to WorkflowNodeData union
- Add default data with single rule (mode: contains, empty value)
- Add default dimensions (260x180, wider than Switch for mode dropdown)
- Add executeConditionalSwitch passthrough executor
- Add conditionalSwitch to VALID_NODE_TYPES in validation
- Export ConditionalSwitchNode from components index (component created in next task)
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
8ca36b30bf
  1. 1
      src/components/nodes/index.ts
  2. 2
      src/lib/quickstart/validation.ts
  3. 1
      src/store/execution/index.ts
  4. 11
      src/store/execution/simpleNodeExecutors.ts
  5. 17
      src/store/utils/nodeDefaults.ts
  6. 26
      src/types/nodes.ts

1
src/components/nodes/index.ts

@ -19,4 +19,5 @@ export { VideoTrimNode } from "./VideoTrimNode";
export { VideoFrameGrabNode } from "./VideoFrameGrabNode";
export { RouterNode } from "./RouterNode";
export { SwitchNode } from "./SwitchNode";
export { ConditionalSwitchNode } from "./ConditionalSwitchNode";
export { GroupNode } from "./GroupNode";

2
src/lib/quickstart/validation.ts

@ -33,6 +33,7 @@ const VALID_NODE_TYPES: NodeType[] = [
"videoFrameGrab",
"router",
"switch",
"conditionalSwitch",
"glbViewer",
];
@ -61,6 +62,7 @@ const DEFAULT_DIMENSIONS: Record<NodeType, { width: number; height: number }> =
videoFrameGrab: { width: 320, height: 320 },
router: { width: 200, height: 80 },
switch: { width: 220, height: 120 },
conditionalSwitch: { width: 260, height: 180 },
glbViewer: { width: 360, height: 380 },
};

1
src/store/execution/index.ts

@ -19,6 +19,7 @@ export {
executeGlbViewer,
executeRouter,
executeSwitch,
executeConditionalSwitch,
} from "./simpleNodeExecutors";
export { executeNanoBanana } from "./nanoBananaExecutor";

11
src/store/execution/simpleNodeExecutors.ts

@ -337,6 +337,17 @@ export async function executeSwitch(ctx: NodeExecutionContext): Promise<void> {
ctx.updateNodeData(ctx.node.id, { status: "complete" });
}
/**
* ConditionalSwitch node: pure passthrough with text-based rule matching.
*/
export async function executeConditionalSwitch(ctx: NodeExecutionContext): Promise<void> {
// ConditionalSwitch is pure passthrough — actual text matching happens during connectedInputs traversal.
// Brief status flash to show execution occurred.
ctx.updateNodeData(ctx.node.id, { status: "loading" });
await new Promise(resolve => setTimeout(resolve, 50));
ctx.updateNodeData(ctx.node.id, { status: "complete" });
}
/**
* GLB Viewer node: receives 3D model URL from upstream, fetches and loads it.
*/

17
src/store/utils/nodeDefaults.ts

@ -20,6 +20,7 @@ import {
VideoFrameGrabNodeData,
RouterNodeData,
SwitchNodeData,
ConditionalSwitchNodeData,
GLBViewerNodeData,
WorkflowNodeData,
GroupColor,
@ -53,6 +54,7 @@ export const defaultNodeDimensions: Record<NodeType, { width: number; height: nu
videoFrameGrab: { width: 320, height: 320 },
router: { width: 200, height: 80 },
switch: { width: 220, height: 120 },
conditionalSwitch: { width: 260, height: 180 },
glbViewer: { width: 360, height: 380 },
};
@ -296,6 +298,21 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
{ id: Math.random().toString(36).slice(2, 9), name: "Output 1", enabled: true }
]
} as SwitchNodeData;
case "conditionalSwitch":
return {
customTitle: null,
comment: null,
incomingText: null,
rules: [
{
id: "rule-" + Math.random().toString(36).slice(2, 9),
value: "",
mode: "contains",
label: "Rule 1",
isMatched: false,
}
]
} as ConditionalSwitchNodeData;
case "glbViewer":
return {
glbUrl: null,

26
src/types/nodes.ts

@ -43,6 +43,7 @@ export type NodeType =
| "videoFrameGrab"
| "router"
| "switch"
| "conditionalSwitch"
| "generate3d"
| "glbViewer";
@ -370,6 +371,30 @@ export interface SwitchNodeData extends BaseNodeData {
}>;
}
/**
* Match mode for conditional switch rules
*/
export type MatchMode = "exact" | "contains" | "starts-with" | "ends-with";
/**
* Conditional switch rule for text-based routing
*/
export interface ConditionalSwitchRule {
id: string; // Unique handle ID, prefixed with "rule-" to avoid collision with reserved "default" keyword
value: string; // Comma-separated match values
mode: MatchMode; // Match strategy
label: string; // User-editable display name
isMatched: boolean; // Computed match state
}
/**
* Conditional Switch node - text-based routing with multi-mode matching
*/
export interface ConditionalSwitchNodeData extends BaseNodeData {
incomingText: string | null; // Upstream text for evaluation and display
rules: ConditionalSwitchRule[]; // User-defined rules
}
/**
* Split Grid node - splits image into grid cells for parallel processing
*/
@ -430,6 +455,7 @@ export type WorkflowNodeData =
| VideoFrameGrabNodeData
| RouterNodeData
| SwitchNodeData
| ConditionalSwitchNodeData
| GLBViewerNodeData;
/**

Loading…
Cancel
Save