Reference for the three-section node configuration UI (Input / Parameters / Output). Companion test suite in client/src/hooks/tests/ and client/src/components/parameterPanel/tests/.
The Parameter Panel is the modal that opens when a node is selected on the canvas. It has three columns that can be hidden independently depending on the node type:
+---------------------------------------------------------------+
| header: icon + name + Run / Save / Cancel |
+----------------+--------------------------+-------------------+
| Input section | Middle section | Output section |
| (left) | (parameters / config) | (right) |
| flex 0.7 | flex 1.6 | flex 0.7 |
+----------------+--------------------------+-------------------+
Files:
- client/src/ParameterPanel.tsx — modal shell
- client/src/components/parameterPanel/ParameterPanelLayout.tsx — flex layout
- client/src/components/parameterPanel/InputSection.tsx
- client/src/components/parameterPanel/MiddleSection.tsx
- client/src/components/parameterPanel/OutputSection.tsx
- client/src/components/output/OutputPanel.tsx — drag source for connected outputs
- client/src/components/ParameterRenderer.tsx — universal widget
- client/src/hooks/useParameterPanel.ts
- client/src/hooks/useDragVariable.ts
- User clicks a node on the canvas →
selectedNodeset in Zustand store. ParameterPanelmounts →useParameterPanel()fires.- Hook reads defaults from
nodeDefinition.properties[].default, then asks backend for any saved parameters via WebSocketget_node_parameters. Saved values overlay defaults. - Modal renders three sections;
MiddleSectionfilters parameters viadisplayOptions.show(see §4 invariants), then renders each visible parameter throughParameterRenderer. - User edits →
handleParameterChange(name, value)updates local state.hasUnsavedChangesflips true (computed viaJSON.stringifyequality with original). - Save → WebSocket
save_node_parameters→ DB; on successoriginalParametersupdated. - Run → if
hasUnsavedChangessave first, thenexecuteNodeViaWebSocket. - Cancel → revert pending edits, clear selection, close modal.
| Node type bucket | Input | Middle | Output |
|---|---|---|---|
| Start | hidden | shown | hidden |
| Skill (e.g. masterSkill, single skill nodes) | hidden | shown | hidden |
Monitor (teamMonitor) |
hidden | shown | hidden |
| Everything else | shown | shown | shown |
ParameterPanel.tsx lines 119–122 compute showInputSection / showOutputSection and pass them
to ParameterPanelLayout.
When the user drags a value from OutputPanel into a parameter input, the dragged payload is a
template string {{name.path}} plus a JSON sidecar with metadata.
name is resolved by useDragVariable.getTemplateVariableName(sourceNodeId) with this strict
priority:
node.data.label— user-renamed labelnodeDefinition.displayName— built-in display namenodeType— registered type namenodeId— final fallback
In every case the result is lowercased and whitespace-stripped ('My Cron Scheduler' →
'mycronscheduler').
The drag payload is set on both MIME types:
text/plain→ the template string{{name.path}}(used by simple text inputs)application/json→{type: 'nodeVariable', nodeId, nodeName, key, variableTemplate, dataType}
effectAllowed is 'copy'.
Each INodeProperties entry can include a displayOptions.show map. Values can be arrays
(allowed-values list) or scalars (single allowed value). All conditions must hold:
displayOptions: {
show: {
operation: ['create', 'update'], // operation must be one of these
useProxy: [true], // AND useProxy must be true
}
}When ALL conditions match the parameter renders; otherwise it's hidden.
A parameter without displayOptions.show always renders.
MiddleSection.shouldShowParameter (lines 59–81) implements this. The function is internal so
tests assert it indirectly via component rendering — see
client/src/components/parameterPanel/tests/MiddleSection.test.tsx.
Both InputSection and OutputPanel walk the workflow's edges to figure out which other nodes
are linked to the current one. They classify handles into two buckets:
| Handle bucket | Examples | Effect |
|---|---|---|
| Data flow | input-main, input-chat, input-task, input-teammates |
shown as connected nodes |
| Config / auxiliary | input-memory, input-tools, input-skill, input-model |
hidden — they belong to the dedicated UI in MiddleSection |
Plus a special case for config nodes themselves (e.g. simpleMemory, any node whose group
includes 'memory' or 'tool'): when the user is viewing a config node, the panel inherits the
parent agent's main inputs and labels them (via Agent Name) so the user can still drag those
upstream variables into the config node's parameters.
OutputSection combines two sources of execution data:
executionResults— local results from in-modal Run button.nodeStatuses[selectedNode.id]— push updates from workflow runs via WebSocket.
The WebSocket result is folded in at the front (newest-first) only when its outputs field
isn't already present in executionResults (deduplicated via JSON.stringify). Statuses other
than success/error (e.g. running) are ignored.
Rendering lives in client/src/components/output/OutputPanel.tsx
(the active renderer — ui/OutputDisplayPanel.tsx is legacy and unimported). The Response
section picks, in order: response / output / text / content (prose keys), then an
object-typed result (the canonical payload key CLI nodes fill with server-side-parsed JSON —
arrays survive unwrap un-peeled and surface here), then stdout. Objects/arrays render in the
themed @uiw/react-json-view tree. Strings render through ReactMarkdown unless the node's
NodeSpec declares uiHints.outputMode = "terminal" (CLI-wrapper plugins: githubAction,
vercelAction, shell) — then they render preformatted in a <pre> on the per-theme
--code-* surface, with wholly-JSON strings detected via the shared tryParseJson
(utils/formatters.ts) and routed to the tree instead. The panel resolves the spec via
useNodeSpec(selectedNode?.type) — backend owns display logic, no node-name checks.
Locked in by the test suite at:
- client/src/hooks/tests/useDragVariable.test.ts
- client/src/components/parameterPanel/tests/MiddleSection.test.tsx
- client/src/components/parameterPanel/tests/InputSection.test.tsx
- client/src/components/parameterPanel/tests/OutputSection.test.tsx
- Defaults loaded from
nodeDefinition.properties[].default; missing default ⇒null. - Saved params win over defaults when merged (DB is source of truth).
hasUnsavedChangesis a deep-equal check against the original snapshot loaded from DB.- Save routes to
save_node_parametersand updates the original snapshot on success. - Cancel restores the pending edits and clears
selectedNode. - Drag template variable uses the priority
label > displayName > nodeType > nodeId, normalised to lowercase + no whitespace. - Drag payload sets both
text/plainandapplication/json;effectAllowed = 'copy'. displayOptions.showhides a parameter unless ALL keyed conditions match. Array values are membership checks; scalar values are equality checks.- Config handles (
input-memory|tools|skill|model) are SKIPPED by bothInputSectionandOutputPanelfor agent nodes — those dependencies surface in MiddleSection. input-main|chat|task|teammatesare NEVER skipped — they are data flow.- Memory / tool config nodes inherit their parent agent's main inputs and label them
via <Agent Name>so upstream variables remain draggable. - OutputSection deduplication compares result
outputsviaJSON.stringifybefore folding in WebSocket status into local results. Onlysuccess/errorstatuses fold in.
- Run disabled while
isExecuting. Prefixed by an autosave whenhasUnsavedChanges. - Save disabled when
!hasUnsavedChanges. - Cancel/Stop acts as Stop (cancels event-wait via WebSocket) when the node is in
waitingstate; otherwise plain Cancel that reverts edits and closes the modal.
cd client
npm install
npm run test:run -- src/hooks/__tests__/useDragVariable.test.ts \
src/components/parameterPanel/__tests__Or use the dedicated script (added in client/package.json):
npm run test:nodepanels