Files
OpenNoodl/.clinerules

1082 lines
28 KiB
Plaintext

# Cline Development Guidelines for OpenNoodl
## Overview
This document provides guidelines for AI-assisted development on the OpenNoodl codebase using Cline in VSCode. Follow these guidelines to ensure consistent, well-documented, and testable contributions.
**🚨 CRITICAL: OpenNoodl Editor is an Electron Desktop Application**
- The editor is NOT a web app - never try to open it in a browser
- Running `npm run dev` launches the Electron app automatically
- Use Electron DevTools (View → Toggle Developer Tools) for debugging
- The viewer/runtime creates web apps, but the editor itself is always Electron
- Never use `browser_action` tool to test the editor - it only works for Storybook or deployed viewers
---
## 1. Before Starting Any Task
### 1.1 Understand the Context
```bash
# Always check which branch you're on
git branch
# Check for uncommitted changes
git status
# Review recent commits
git log --oneline -10
```
### 1.2 Read Relevant Documentation
Before modifying any file, understand its purpose:
1. Check for README files in the package
2. Read JSDoc comments on functions
3. Look for related test files
4. Search for usage patterns: `grep -r "functionName" packages/`
### 1.3 Identify Dependencies
```bash
# Check what imports a file
grep -r "from.*filename" packages/
# Check what the file imports
head -50 path/to/file.ts | grep "import"
```
---
## 2. Code Style Requirements
### 2.1 TypeScript Standards
```typescript
// ✅ GOOD: Explicit types
interface NodeProps {
id: string;
type: NodeType;
connections: Connection[];
}
function processNode(node: NodeProps): ProcessedNode {
// ...
}
// ❌ BAD: Implicit any
function processNode(node) {
// ...
}
// ❌ BAD: Using TSFixme
function processNode(node: TSFixme): TSFixme {
// ...
}
```
### 2.2 React Component Standards
```tsx
// ✅ GOOD: Functional component with types
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
export function Button({ label, onClick, disabled = false }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{label}
</button>
);
}
// ❌ BAD: Class component (unless necessary for lifecycle)
class Button extends React.Component {
// ...
}
```
### 2.3 Import Organization
```typescript
// 1. External packages (alphabetical)
import classNames from 'classnames';
import React, { useCallback, useState } from 'react';
import { NodeGraphModel } from '@noodl-models/nodegraphmodel';
import { KeyCode } from '@noodl-utils/keyboard/KeyCode';
// 2. Internal packages (alphabetical by alias)
import { IconName } from '@noodl-core-ui/components/common/Icon';
import css from './Component.module.scss';
// 3. Relative imports (by depth, then alphabetical)
import { localHelper } from './helpers';
```
### 2.4 Naming Conventions
| Type | Convention | Example |
| ----------- | --------------------- | ------------------------- |
| Components | PascalCase | `NodeEditor.tsx` |
| Hooks | camelCase, use prefix | `useNodeSelection.ts` |
| Utils | camelCase | `formatNodeName.ts` |
| Constants | UPPER_SNAKE | `MAX_CONNECTIONS` |
| CSS Modules | kebab-case | `node-editor.module.scss` |
| Test files | Same + .test | `NodeEditor.test.tsx` |
---
## 3. Documentation Requirements
### 3.1 File Headers
Every new file should have a header comment:
```typescript
/**
* NodeProcessor
*
* Handles the processing of node graph updates and manages
* the execution order of connected nodes.
*
* @module noodl-runtime
* @since 1.2.0
*/
```
### 3.2 Function Documentation
````typescript
/**
* Processes a node and propagates changes to connected nodes.
*
* @param node - The node to process
* @param context - The execution context
* @param options - Processing options
* @param options.force - Force re-evaluation even if inputs unchanged
* @returns The processed output values
* @throws {NodeProcessingError} If the node definition is invalid
*
* @example
* ```typescript
* const output = processNode(myNode, context, { force: true });
* console.log(output.value);
* ```
*/
function processNode(node: NodeInstance, context: ExecutionContext, options: ProcessOptions = {}): NodeOutput {
// ...
}
````
### 3.3 Complex Logic Comments
```typescript
// Calculate the topological sort order for node evaluation.
// This ensures nodes are processed after their dependencies.
// Uses Kahn's algorithm for O(V+E) complexity.
const sortedNodes = topologicalSort(nodes, connections);
```
---
## 4. Testing Requirements
### 4.1 Test File Location
Tests should be co-located or in a parallel `tests/` directory:
```
// Option A: Co-located
components/
├── Button/
│ ├── Button.tsx
│ ├── Button.test.tsx
│ └── Button.module.scss
// Option B: Parallel (current pattern in noodl-editor)
packages/noodl-editor/
├── src/
│ └── components/Button.tsx
└── tests/
└── components/Button.test.ts
```
### 4.2 Test Structure
```typescript
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
import { renderHook, act } from '@testing-library/react-hooks';
describe('useNodeSelection', () => {
// Setup
let mockContext: NodeGraphContext;
beforeEach(() => {
mockContext = createMockContext();
});
// Group related tests
describe('when selecting a single node', () => {
it('should update selection state', () => {
const { result } = renderHook(() => useNodeSelection(mockContext));
act(() => {
result.current.selectNode('node-1');
});
expect(result.current.selectedNodes).toContain('node-1');
});
it('should clear previous selection by default', () => {
// ...
});
});
describe('when multi-selecting nodes', () => {
// ...
});
});
```
### 4.3 What to Test
| Priority | What to Test |
| -------- | ---------------------- |
| High | Utility functions |
| High | Data transformations |
| High | State management logic |
| Medium | React hooks |
| Medium | Component behavior |
| Low | Pure UI rendering |
---
## 5. Git Workflow
### 5.1 Branch Naming
```bash
# Features
git checkout -b feature/add-vercel-deployment
# Bug fixes
git checkout -b fix/page-router-scroll
# Refactoring
git checkout -b refactor/remove-tsfixme-panels
# Documentation
git checkout -b docs/update-node-api
```
### 5.2 Commit Messages
Follow conventional commits:
```bash
# Format: type(scope): description
# Features
git commit -m "feat(editor): add breakpoint support for node connections"
# Bug fixes
git commit -m "fix(viewer): resolve scroll position reset in nested Page Router"
# Refactoring
git commit -m "refactor(runtime): replace TSFixme with proper types in node processor"
# Documentation
git commit -m "docs(api): add JSDoc to all public node methods"
# Tests
git commit -m "test(editor): add unit tests for node selection hook"
# Chores
git commit -m "chore(deps): update react to 19.0.0"
```
### 5.3 Commit Frequency
- Commit after each logical change
- Don't combine unrelated changes
- Commit working states (tests should pass)
---
## 6. Codebase Navigation
### 6.1 Key Directories
```
packages/
├── noodl-editor/
│ ├── src/
│ │ ├── editor/src/
│ │ │ ├── models/ # Data models (ProjectModel, NodeGraph, etc.)
│ │ │ ├── views/ # UI components and views
│ │ │ ├── utils/ # Helper utilities
│ │ │ ├── store/ # State stores (AI Assistant, etc.)
│ │ │ └── pages/ # Page-level components
│ │ ├── main/ # Electron main process
│ │ └── shared/ # Shared utilities
│ └── tests/ # Test files
├── noodl-runtime/
│ └── src/
│ ├── nodes/ # Runtime node definitions
│ └── nodecontext.js # Execution context
├── noodl-viewer-react/
│ └── src/
│ └── nodes/ # React-based visual nodes
└── noodl-core-ui/
└── src/
└── components/ # Shared UI components
```
### 6.2 Finding Things
```bash
# Find a component
find packages/ -name "*NodeEditor*" -type f
# Find where something is imported
grep -r "import.*from.*NodeEditor" packages/
# Find where a function is called
grep -r "processNode(" packages/ --include="*.ts" --include="*.tsx"
# Find all TODO comments
grep -rn "TODO\|FIXME" packages/noodl-editor/src
# Find test files
find packages/ -name "*.test.ts" -o -name "*.spec.ts"
```
### 6.3 Understanding Data Flow
1. **User Action** → `views/` components capture events
2. **State Update** → `models/` handle business logic
3. **Runtime Sync** → `ViewerConnection` sends to preview
4. **Persistence** → `ProjectModel` saves to disk
---
## 7. Common Patterns
### 7.1 Event Handling Pattern
```typescript
// Models use EventDispatcher for pub/sub
import { EventDispatcher } from '../../../shared/utils/EventDispatcher';
class MyModel extends EventDispatcher {
doSomething() {
// ... logic
this.notifyListeners('updated', { data: result });
}
}
// Usage
const model = new MyModel();
model.on('updated', (data) => {
console.log('Model updated:', data);
});
```
### 7.2 React Hook Pattern
```typescript
// Custom hook for model subscription
function useModel<T>(model: EventDispatcher, event: string): T {
const [state, setState] = useState<T>(model.getState());
useEffect(() => {
const handler = (newState: T) => setState(newState);
model.on(event, handler);
return () => model.off(event, handler);
}, [model, event]);
return state;
}
```
### 7.3 Node Definition Pattern
```javascript
// In noodl-runtime/src/nodes/
const MyNode = {
name: 'My.Custom.Node',
displayName: 'My Custom Node',
category: 'Custom',
inputs: {
inputValue: {
type: 'string',
displayName: 'Input Value',
default: ''
}
},
outputs: {
outputValue: {
type: 'string',
displayName: 'Output Value'
}
},
methods: {
setInputValue(value) {
this._internal.inputValue = value;
this.flagOutputDirty('outputValue');
}
},
getOutputValue(name) {
if (name === 'outputValue') {
return this._internal.inputValue.toUpperCase();
}
}
};
```
---
## 8. Error Handling
### 8.1 User-Facing Errors
```typescript
import { ToastLayer } from '../views/ToastLayer/ToastLayer';
try {
await riskyOperation();
} catch (error) {
// Log for debugging
console.error('Operation failed:', error);
// Show user-friendly message
ToastLayer.showError('Unable to complete operation. Please try again.');
}
```
### 8.2 Developer Errors
```typescript
// Use assertions for developer errors
function processNode(node: NodeInstance) {
if (!node.id) {
throw new Error(`processNode: node.id is required`);
}
if (!node.definition) {
throw new Error(`processNode: node "${node.id}" has no definition`);
}
}
```
### 8.3 Graceful Degradation
```typescript
function getNodeIcon(node: NodeInstance): string {
try {
return node.definition.icon || 'default-icon';
} catch {
console.warn(`Could not get icon for node ${node.id}`);
return 'default-icon';
}
}
```
---
## 9. Performance Considerations
### 9.1 Avoid Unnecessary Re-renders
```tsx
// ✅ GOOD: Memoized callback
const handleClick = useCallback(() => {
onNodeSelect(node.id);
}, [node.id, onNodeSelect]);
// ✅ GOOD: Memoized expensive computation
const sortedNodes = useMemo(() => {
return topologicalSort(nodes);
}, [nodes]);
// ❌ BAD: New function on every render
<Button onClick={() => onNodeSelect(node.id)} />;
```
### 9.2 Lazy Loading
```tsx
// Lazy load heavy components
const CodeEditor = React.lazy(() => import('./CodeEditor'));
function EditorPanel() {
return (
<Suspense fallback={<LoadingSpinner />}>
<CodeEditor />
</Suspense>
);
}
```
### 9.3 Batch Updates
```typescript
// Batch multiple state updates
import { unstable_batchedUpdates } from 'react-dom';
unstable_batchedUpdates(() => {
setSelection(newSelection);
setHighlight(newHighlight);
setZoom(newZoom);
});
```
---
## 10. Checklist Before Submitting
### Code Quality
- [ ] No `TSFixme` types added
- [ ] All new functions have JSDoc comments
- [ ] Complex logic has inline comments
- [ ] No console.log statements (except errors/warnings)
- [ ] No unused imports or variables
### Testing
- [ ] Unit tests for new utility functions
- [ ] Integration tests for new features
- [ ] Existing tests still pass
- [ ] Manual testing completed
### Documentation
- [ ] README updated if needed
- [ ] JSDoc added to public APIs
- [ ] Comments explain "why", not "what"
### Git
- [ ] Meaningful commit messages
- [ ] No unrelated changes in commits
- [ ] Branch named correctly
- [ ] Based on latest main branch
### Performance
- [ ] No obvious performance regressions
- [ ] Large lists use virtualization
- [ ] Expensive computations are memoized
### React + EventDispatcher (Phase 0 Critical Bugs)
- [ ] Using `useEventListener` hook for ALL EventDispatcher subscriptions (NOT direct `.on()`)
- [ ] Singleton instances included in useEffect dependencies (e.g., `[ProjectModel.instance]`)
- [ ] Using `UndoQueue.instance.pushAndDo()` pattern (NOT `undoGroup.push()` + `undoGroup.do()`)
- [ ] No direct EventDispatcher `.on()` calls in React components
- [ ] Event subscriptions verified with debug logging
---
## Quick Reference Commands
```bash
# Development
npm run dev # Start editor with hot reload
npm run test:editor # Run tests
npm run build:editor # Production build
# Code Quality
npx eslint packages/noodl-editor/src --fix
npx prettier --write "packages/**/*.{ts,tsx}"
npx tsc --noEmit # Type check
# Debugging
DEBUG=* npm run dev # Verbose logging
npm run test:editor -- --verbose
# Finding Issues
grep -r "TSFixme" packages/ # Find type escapes
grep -r "any" packages/ --include="*.ts" | head -20
```
## 11. Additional system instructions and critical development files
dev-docs/
├── reference/
│ ├── CODEBASE-MAP.md # OpenNoodl Codebase Quick Navigation
│ ├── COMMON-ISSUES.md # Solutions to frequently encountered problems when developing OpenNoodl.
│ ├── NODE-PATTERNS.md # How to create and modify nodes in OpenNoodl.
├── guidelines/
│ ├── CODING-STANDARDS.md # This document defines the coding style and patterns for OpenNoodl development.
│ ├── GIT-WORKFLOW.md # How to manage branches, commits, and pull requests for OpenNoodl development.
├── TASK-TEMPLATE.md # Use this template to create new task documentation. Copy the entire `TASK-XXX-template/` folder and rename it.
## 12. Institutional Learning
### Discovering & Recording Knowledge
As you work through tasks in this large codebase, you WILL discover things that aren't documented:
- Why something was built a certain way
- Hidden gotchas or edge cases
- Patterns that aren't obvious
- Fixes for confusing errors
- Relationships between distant parts of the code
**When you learn something useful, write it down immediately.**
Add discoveries to: `dev-docs/reference/LEARNINGS.md`
Format each entry:
```
### [Date] - [Brief Title]
**Context**: What were you trying to do?
**Discovery**: What did you learn?
**Location**: What files/areas does this apply to?
**Keywords**: [searchable terms]
```
Examples of things worth recording:
- "The `scheduleAfterInputsHaveUpdated` pattern is required when multiple inputs might change in the same frame"
- "RouterAdapter.ts secretly depends on component naming conventions - pages must be in folders"
- "React 19 automatic batching breaks the old `forceUpdate` pattern in nodegrapheditor"
- "Collection change events don't fire if you mutate items directly - must use `.set()`"
### Using Accumulated Knowledge
**Before struggling with something complex, check the learnings:**
1. Read `dev-docs/reference/LEARNINGS.md`
2. Search for relevant keywords
3. Check if someone already solved this problem
**When hitting a confusing error:**
1. Search LEARNINGS.md for the error message or related terms
2. Check `dev-docs/reference/COMMON-ISSUES.md`
3. If you solve it and it's not documented, ADD IT
### What Makes Good Learnings
✅ **Worth recording:**
- Non-obvious behavior ("X only works if Y is true")
- Error solutions that took time to figure out
- Undocumented dependencies between systems
- Performance gotchas
- Patterns you had to reverse-engineer
❌ **Not worth recording:**
- Basic TypeScript/React knowledge
- Things already in official docs
- One-off typos or simple mistakes
- Task-specific details (those go in task CHANGELOG)
### Building the Knowledge Base
Over time, LEARNINGS.md may grow large. When it does:
- Group related entries under headings
- Move mature topics to dedicated docs (e.g., `LEARNINGS.md` entry about data nodes → `DATA-SYSTEM-DEEP-DIVE.md`)
- Cross-reference from COMMON-ISSUES.md
The goal: **No one should have to solve the same puzzle twice.**
---
---
## 13. UI Styling Rules
> **CRITICAL:** Before any UI/CSS work, read `dev-docs/reference/UI-STYLING-GUIDE.md`
### 13.1 Never Use Hardcoded Colors
```scss
// ❌ BAD - copying legacy patterns
.Card {
background-color: #27272a;
color: #b8b8b8;
}
// ✅ GOOD - using design tokens
.Card {
background-color: var(--theme-color-bg-3);
color: var(--theme-color-fg-default);
}
```
### 13.2 Quick Token Reference
| Purpose | Token |
| ----------------- | ------------------------------ |
| Panel backgrounds | `--theme-color-bg-2` |
| Card backgrounds | `--theme-color-bg-3` |
| Normal text | `--theme-color-fg-default` |
| Secondary text | `--theme-color-fg-default-shy` |
| Emphasized text | `--theme-color-fg-highlight` |
| Primary buttons | `--theme-color-primary` |
| Borders | `--theme-color-border-default` |
### 13.3 Legacy Files Warning
DO NOT copy patterns from these files (they have hardcoded colors):
- `packages/noodl-editor/src/editor/src/styles/popuplayer.css`
- `packages/noodl-editor/src/editor/src/styles/propertyeditor.css`
DO reference these files (they use proper patterns):
- `packages/noodl-core-ui/src/components/layout/BaseDialog/`
- `packages/noodl-core-ui/src/components/inputs/PrimaryButton/`
### 13.4 Before Completing UI Tasks
Verify:
- [ ] No hardcoded hex colors (`grep -E '#[0-9a-fA-F]{3,6}' your-file.scss`)
- [ ] All colors use `var(--theme-color-*)` tokens
- [ ] Hover/focus/disabled states defined
---
## Section: React + EventDispatcher Integration
````markdown
## React + EventDispatcher Integration
### CRITICAL: Always use useEventListener hook
When subscribing to EventDispatcher events from React components, ALWAYS use the `useEventListener` hook. Direct subscriptions silently fail.
**Hook location:** `@noodl-hooks/useEventListener`
**✅ CORRECT - Always do this:**
```typescript
import { useEventListener } from '@noodl-hooks/useEventListener';
import { ProjectModel } from '@noodl-models/projectmodel';
function MyComponent() {
useEventListener(ProjectModel.instance, 'componentRenamed', (data) => {
// This works!
});
}
```
````
**❌ BROKEN - Never do this:**
```typescript
// This compiles and runs without errors, but events are NEVER received
useEffect(() => {
const context = {};
ProjectModel.instance.on('event', handler, context);
return () => ProjectModel.instance.off(context);
}, []);
```
### Why this matters
EventDispatcher uses a context-object cleanup pattern incompatible with React closures. Direct subscriptions fail silently - no errors, no events, just confusion.
This pattern was established in Phase 0 after discovering the issue in TASK-004B.
### Available dispatchers
- `ProjectModel.instance` - component changes, settings
- `NodeLibrary.instance` - library/module changes
- `WarningsModel.instance` - validation warnings
- `EventDispatcher.instance` - global events
- `UndoQueue.instance` - undo/redo state
### Full documentation
See: `dev-docs/patterns/REACT-EVENTDISPATCHER.md`
````
---
## Section: Webpack Cache Issues
```markdown
## Webpack Cache Issues
### If code changes don't appear
When editing code and changes don't load in the running app:
1. **First, run `npm run clean:all`** - This nukes all caches
2. **Restart the dev server** - Don't just refresh
3. **Check for the build canary** - Console should show `🔥 BUILD TIMESTAMP: [recent time]`
If the canary shows an old timestamp, caching is still an issue. Check:
- Electron app cache (platform-specific location)
- Any lingering node/Electron processes (`pkill -f node; pkill -f Electron`)
- Browser cache (hard refresh with Cmd+Shift+R)
### Never debug without verifying fresh code
Before spending time debugging, ALWAYS verify your code changes are actually running:
1. Add a distinctive console.log: `console.log('🔥 MY CHANGE LOADED:', Date.now())`
2. Save the file
3. Check if the log appears
4. If not, clear caches and restart
This avoids wasting hours debugging stale code.
### Webpack config notes
- Dev mode should NOT use `cache: { type: 'filesystem' }`
- Memory cache or no cache is preferred for development
- Production can use filesystem cache for CI speed
````
---
## Section: Foundation Health
```markdown
## Foundation Health Check
### When to run
Run `npm run health:check` when:
- Starting work after a break
- After updating dependencies
- When things "feel broken"
- Before investigating mysterious bugs
### What it checks
1. Cache state (not stale/oversized)
2. Webpack config (correct cache settings)
3. useEventListener hook (present and exported)
4. Direct EventDispatcher subscriptions (anti-pattern detection)
5. Build canary (present in entry)
6. Package versions (no known problematic versions)
### Interpreting results
- ✅ Pass: All good
- ⚠️ Warning: Works but could be improved
- ❌ Fail: Must fix before proceeding
```
---
## Section: Debugging React Migrations
````markdown
## Debugging Legacy → React Migrations
### Common issue: UI doesn't update after action
If you perform an action (rename, add, delete) and the UI doesn't update:
1. **Check if the action succeeded** - Look in console for success logs
2. **Check if event was emitted** - Add logging to the model method
3. **Check if event was received** - Add logging in useEventListener callback
4. **Check if component re-rendered** - Add console.log in component body
Usually the problem is:
- ❌ Using direct `.on()` instead of `useEventListener`
- ❌ Cached old code running (run `npm run clean:all`)
- ❌ Event name mismatch (check exact spelling)
### Pattern for debugging event subscriptions
```typescript
useEventListener(ProjectModel.instance, 'componentRenamed', (data) => {
console.log('🔔 Event received:', data); // Add this temporarily
// Your actual handler
});
```
````
If you don't see the log, the subscription isn't working.
```
---
_Last Updated: December 2025_
```
---
## 14. Node Creation Checklist
> **🚨 CRITICAL:** Before creating or modifying runtime nodes, read `dev-docs/reference/LEARNINGS-NODE-CREATION.md`
Creating nodes in OpenNoodl is deceptively tricky. This checklist prevents the most common (and hardest to debug) issues.
### 14.1 Pre-Flight Checklist
Before writing any node code:
- [ ] Read `dev-docs/reference/LEARNINGS-NODE-CREATION.md` (especially the CRITICAL GOTCHAS section)
- [ ] Study an existing working node of similar complexity (e.g., `restnode.js` for data nodes)
- [ ] Understand the difference between `inputs` (static) vs `prototypeExtensions` (instance methods)
- [ ] Know where your node should be registered (noodl-runtime vs noodl-viewer-react)
### 14.2 Input Handler Rules
```javascript
// ✅ CORRECT: Signal inputs use valueChangedToTrue
inputs: {
fetch: {
type: 'signal',
valueChangedToTrue: function() {
this.scheduleFetch();
}
}
}
// ❌ WRONG: Signal inputs with set() - NEVER TRIGGERS
inputs: {
fetch: {
type: 'signal',
set: function(value) { // ☠️ Never called for signals
this.scheduleFetch();
}
}
}
```
### 14.3 Never Override setInputValue
```javascript
// ❌ BREAKS EVERYTHING - Never define setInputValue in prototypeExtensions
prototypeExtensions: {
setInputValue: function(name, value) { // ☠️ Overrides base - signals stop working
// ...
}
}
// ✅ Use a different name for custom storage
prototypeExtensions: {
_storeInputValue: function(name, value) { // ✅ Doesn't override anything
this._internal.inputValues[name] = value;
}
}
```
### 14.4 Dynamic Ports Must Include Static Ports
```javascript
// ❌ WRONG - Static ports disappear
function updatePorts(nodeId, parameters, editorConnection) {
const ports = [];
// Only adds dynamic ports...
editorConnection.sendDynamicPorts(nodeId, ports); // Static inputs gone!
}
// ✅ CORRECT - Include all ports
function updatePorts(nodeId, parameters, editorConnection) {
const ports = [
// Re-add static inputs
{ name: 'url', displayName: 'URL', type: 'string', plug: 'input', group: 'Request' },
{ name: 'fetch', displayName: 'Fetch', type: 'signal', plug: 'input', group: 'Actions' },
// Then add dynamic ports...
];
editorConnection.sendDynamicPorts(nodeId, ports);
}
```
### 14.5 Register Config Inputs Explicitly
```javascript
// Config inputs (from stringlist editors) need explicit registration
registerInputIfNeeded: function(name) {
if (this.hasInput(name)) return;
// Map config names to their setters
const configSetters = {
'method': this.setMethod.bind(this),
'headers': this.setHeaders.bind(this),
'queryParams': this.setQueryParams.bind(this)
};
if (configSetters[name]) {
return this.registerInput(name, { set: configSetters[name] });
}
// Handle prefixed dynamic inputs
if (name.startsWith('header-')) {
return this.registerInput(name, {
set: this._storeInputValue.bind(this, name)
});
}
}
```
### 14.6 Export Format Matters
```javascript
// ✅ CORRECT: Export with setup function
module.exports = {
node: MyNode,
setup: function (context, graphModel) {
// Port management goes here
}
};
// ❌ WRONG: Direct export (setup never runs)
module.exports = MyNode;
```
### 14.7 Post-Creation Verification
After creating a node:
1. **Check ports appear**: All static AND dynamic inputs/outputs visible in editor?
2. **Check signals work**: Add console.log in `valueChangedToTrue` - does it print?
3. **Check config inputs work**: Change dropdown/stringlist values - does setter get called?
4. **Clear caches if needed**: `npm run clean:all` and restart
### 14.8 Quick Reference
| Input Type | Handler | Callback Format |
| ---------------------------- | --------------------------- | --------------------------- |
| Signal | `valueChangedToTrue` | `function() { ... }` |
| Value (string, number, etc.) | `set` | `function(value) { ... }` |
| Enum (dropdown) | `set` | `function(value) { ... }` |
| StringList (config) | Needs explicit registration | Via `registerInputIfNeeded` |
### 14.9 Where to Find Examples
| Pattern | Example File |
| ------------------------------------ | ---------------------------------------------------------------- |
| Complex data node with dynamic ports | `noodl-runtime/src/nodes/std-library/data/restnode.js` |
| HTTP node (fixed, working) | `noodl-runtime/src/nodes/std-library/data/httpnode.js` |
| Simple value node | `noodl-runtime/src/nodes/std-library/variables/numbernode.js` |
| Signal-based node | `noodl-runtime/src/nodes/std-library/timer.js` (in viewer-react) |
---