Merged Axel changes. Added dev docs for Cline

This commit is contained in:
Richard Osborne
2025-12-06 22:37:54 +01:00
parent da40209322
commit 9a5952ec13
15 changed files with 3927 additions and 0 deletions

View File

@@ -0,0 +1,378 @@
# OpenNoodl Codebase Quick Navigation
## 🗺️ Package Map
```
┌─────────────────────────────────────────────────────────────────────────┐
│ MONOREPO ROOT │
├─────────────────────────────────────────────────────────────────────────┤
│ package.json → Workspace config, global scripts │
│ lerna.json → Monorepo management │
│ scripts/ → Build and CI scripts │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────┼───────────────────────────┐
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ EDITOR (GPL) │ │ RUNTIME (MIT) │ │ UI LIBRARY │
│ noodl-editor │ │ noodl-runtime │ │ noodl-core-ui │
│ │ │ │ │ │
│ • Electron app │ │ • Node engine │ │ • React components│
│ • React UI │ │ • Data flow │ │ • Storybook │
│ • Property panels │ │ • Event system │ │ • Styling │
└───────────────────┘ └───────────────────┘ └───────────────────┘
│ │
│ ▼
│ ┌───────────────────┐
│ │ VIEWER (MIT) │
│ │ noodl-viewer-react│
│ │ │
│ │ • React runtime │
│ │ • Visual nodes │
│ │ • DOM handling │
│ └───────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ PLATFORM LAYER │
├───────────────────┬───────────────────┬───────────────────────────────┤
│ noodl-platform │ platform-electron │ platform-node │
│ (abstraction) │ (desktop impl) │ (server impl) │
└───────────────────┴───────────────────┴───────────────────────────────┘
```
---
## 📁 Key Directories
### noodl-editor (Main Application)
```
packages/noodl-editor/src/
├── editor/src/
│ ├── models/ # 🎯 Business logic & data
│ │ ├── projectmodel.ts → Project state
│ │ ├── nodegraphmodel.ts → Graph structure
│ │ ├── componentmodel.ts → Components
│ │ ├── nodelibrary/ → Node type registry
│ │ ├── AiAssistant/ → AI features
│ │ └── sidebar/ → Sidebar state
│ │
│ ├── views/ # 🖥️ UI components
│ │ ├── nodegrapheditor.ts → Canvas/graph editor
│ │ ├── panels/ → Property panels
│ │ ├── NodePicker/ → Node creation UI
│ │ ├── documents/ → Document views
│ │ └── popups/ → Modal dialogs
│ │
│ ├── utils/ # 🔧 Utilities
│ │ ├── CodeEditor/ → Monaco integration
│ │ ├── filesystem.ts → File operations
│ │ └── projectimporter.js → Import/export
│ │
│ ├── store/ # 💾 Persistent state
│ │ └── AiAssistantStore.ts → AI settings
│ │
│ └── pages/ # 📄 Page components
│ └── EditorPage/ → Main editor page
├── main/ # ⚡ Electron main process
│ └── main.js → App entry point
└── shared/ # 🔗 Shared utilities
└── utils/
└── EventDispatcher.ts → Pub/sub system
```
### noodl-runtime (Execution Engine)
```
packages/noodl-runtime/
├── src/
│ ├── nodes/ # 📦 Node implementations
│ │ └── std-library/
│ │ ├── data/ → Data nodes (REST, DB, etc.)
│ │ ├── logic/ → Logic nodes
│ │ └── events/ → Event nodes
│ │
│ ├── node.js # Base node class
│ ├── nodedefinition.js # Node definition API
│ ├── noderegister.js # Node registry
│ ├── nodescope.js # Component scope
│ └── nodecontext.js # Runtime context
└── noodl-runtime.js # Main runtime entry
```
### noodl-viewer-react (React Runtime)
```
packages/noodl-viewer-react/src/
├── nodes/ # 🎨 Visual nodes
│ ├── basic/ → Group, Text, Image
│ ├── controls/ → Button, Input, Checkbox
│ ├── navigation/ → PageRouter, Page
│ └── std-library/ → Standard library nodes
└── react-component-node.js # React node wrapper
```
### noodl-core-ui (Component Library)
```
packages/noodl-core-ui/src/
├── components/
│ ├── common/ # 🧩 Basic components
│ │ ├── Icon/
│ │ └── ActivityIndicator/
│ │
│ ├── inputs/ # 📝 Form controls
│ │ ├── TextInput/
│ │ ├── PrimaryButton/
│ │ └── Checkbox/
│ │
│ ├── layout/ # 📐 Layout components
│ │ ├── Box/
│ │ ├── Container/
│ │ └── Tabs/
│ │
│ ├── popups/ # 💬 Dialogs & menus
│ │ ├── MenuDialog/
│ │ └── PopupToolbar/
│ │
│ └── ai/ # 🤖 AI UI components
│ ├── AiChatBox/
│ └── AiChatMessage/
└── styles/ # 🎨 Global styles
```
---
## 🔍 Finding Things
### Search Patterns
```bash
# Find a file by name
find packages/ -name "*NodeGraph*" -type f
# Find where a function is defined
grep -rn "function processNode" packages/
# Find where something is imported/used
grep -r "import.*from.*nodegraphmodel" packages/
# Find all usages of a component
grep -r "<NodeEditor" packages/ --include="*.tsx"
# Find TODO/FIXME comments
grep -rn "TODO\|FIXME" packages/noodl-editor/src
```
### Common Search Targets
| Looking for... | Search pattern |
|----------------|----------------|
| Node definitions | `packages/noodl-runtime/src/nodes/` |
| React visual nodes | `packages/noodl-viewer-react/src/nodes/` |
| UI components | `packages/noodl-core-ui/src/components/` |
| Models/state | `packages/noodl-editor/src/editor/src/models/` |
| Property panels | `packages/noodl-editor/src/editor/src/views/panels/` |
| Tests | `packages/noodl-editor/tests/` |
---
## 🚀 Quick Commands
### Development
```bash
# Start everything
npm run dev
# Start just the editor (faster)
npm run start:editor
# Start Storybook (UI components)
npm run start:storybook
# Start viewer dev server
npm run start:viewer
```
### Building
```bash
# Build editor
npm run build:editor
# Create distributable package
npm run build:editor:pack
# Build cloud runtime
npm run build:cloud-runtime
```
### Testing
```bash
# Run all editor tests
npm run test:editor
# Run platform tests
npm run test:platform
```
### Code Quality
```bash
# Type check
npx tsc --noEmit
# Lint
npx eslint packages/noodl-editor/src
# Format
npx prettier --write "packages/**/*.{ts,tsx}"
```
---
## 🔗 Key Files Reference
### Configuration
| File | Purpose |
|------|---------|
| `package.json` | Root workspace config |
| `lerna.json` | Monorepo settings |
| `tsconfig.json` | TypeScript config |
| `.eslintrc.js` | Linting rules |
| `.prettierrc` | Code formatting |
### Entry Points
| File | Purpose |
|------|---------|
| `noodl-editor/src/main/main.js` | Electron main process |
| `noodl-editor/src/editor/src/index.js` | Renderer entry |
| `noodl-runtime/noodl-runtime.js` | Runtime engine |
| `noodl-viewer-react/index.js` | React runtime |
### Core Models
| File | Purpose |
|------|---------|
| `projectmodel.ts` | Project state management |
| `nodegraphmodel.ts` | Graph data structure |
| `componentmodel.ts` | Component definitions |
| `nodelibrary.ts` | Node type registry |
### Important Views
| File | Purpose |
|------|---------|
| `nodegrapheditor.ts` | Main canvas editor |
| `EditorPage.tsx` | Main page layout |
| `NodePicker.tsx` | Node creation panel |
| `PropertyEditor/` | Property panels |
---
## 🏷️ Type System
### Key Types (global.d.ts)
```typescript
// TSFixme - Type escape hatch (TO BE REMOVED)
type TSFixme = any;
// Node colors
type NodeColor = 'data' | 'visual' | 'logic' | 'component' | 'javascript';
// CSS modules
declare module '*.scss' {
const styles: { readonly [key: string]: string };
export default styles;
}
```
### Common Interfaces
```typescript
// Node graph structures (nodegraphmodel.ts)
interface NodeGraphNode {
id: string;
type: string;
x: number;
y: number;
parameters: Record<string, any>;
}
interface Connection {
fromId: string;
fromPort: string;
toId: string;
toPort: string;
}
// Component structure (componentmodel.ts)
interface ComponentModel {
name: string;
graph: NodeGraphModel;
metadata: Record<string, any>;
}
```
---
## 📝 Path Aliases
```typescript
// Configured in tsconfig.json
@noodl-models/ packages/noodl-editor/src/editor/src/models/
@noodl-utils/ packages/noodl-editor/src/editor/src/utils/
@noodl-contexts/ packages/noodl-editor/src/editor/src/contexts/
@noodl-hooks/ packages/noodl-editor/src/editor/src/hooks/
@noodl-constants/ packages/noodl-editor/src/editor/src/constants/
@noodl-core-ui/ packages/noodl-core-ui/src/
@noodl/platform packages/noodl-platform/src/
```
---
## 🚨 Common Issues
### Build Problems
```bash
# Clear caches
rm -rf node_modules/.cache
rm -rf packages/*/node_modules/.cache
# Reinstall dependencies
rm -rf node_modules
npm install
```
### TypeScript Errors
```bash
# Check for circular dependencies
npx madge --circular packages/noodl-editor/src
```
### Electron Issues
```bash
# Clear app data (macOS)
rm -rf ~/Library/Application\ Support/OpenNoodl/
# Rebuild native modules
npm run rebuild
```
---
*Quick reference card for OpenNoodl development. Print or pin to your IDE!*

View File

@@ -0,0 +1,253 @@
# Common Issues & Troubleshooting
Solutions to frequently encountered problems when developing OpenNoodl.
## Build Issues
### "Module not found" Errors
**Symptom**: Build fails with `Cannot find module '@noodl-xxx/...'`
**Solutions**:
1. Run `npm install` from root directory
2. Check if package exists in `packages/`
3. Verify tsconfig paths are correct
4. Try: `rm -rf node_modules && npm install`
### "Peer dependency" Warnings
**Symptom**: npm install shows peer dependency warnings
**Solutions**:
1. Check if versions are compatible
2. Update the conflicting package
3. Last resort: `npm install --legacy-peer-deps`
4. Document why in CHANGELOG.md
### TypeScript Errors After Update
**Symptom**: Types that worked before now fail
**Solutions**:
1. Run `npx tsc --noEmit` to see all errors
2. Check if `@types/*` packages need updating
3. Look for breaking changes in updated packages
4. Check `tsconfig.json` for configuration issues
### Webpack Build Hangs
**Symptom**: Build starts but never completes
**Solutions**:
1. Check for circular imports: `npx madge --circular packages/`
2. Increase Node memory: `NODE_OPTIONS=--max_old_space_size=4096`
3. Check for infinite loops in build scripts
4. Try building individual packages
## Runtime Issues
### Hot Reload Not Working
**Symptom**: Changes don't appear without full restart
**Solutions**:
1. Check webpack dev server is running
2. Verify file is being watched (check webpack config)
3. Clear browser cache
4. Check for syntax errors preventing reload
5. Restart dev server: `npm run dev`
### Node Not Appearing in Picker
**Symptom**: Created a node but it doesn't show up
**Solutions**:
1. Verify node is exported in `nodelibraryexport.js`
2. Check `category` is valid
3. Verify no JavaScript errors in node definition
4. Restart the editor
### "Cannot read property of undefined"
**Symptom**: Runtime error accessing object properties
**Solutions**:
1. Add null checks: `obj?.property`
2. Verify data is loaded before access
3. Check async timing issues
4. Add defensive initialization
### State Not Updating
**Symptom**: Changed input but output doesn't update
**Solutions**:
1. Verify `flagOutputDirty()` is called
2. Check if batching is interfering
3. Verify connection exists in graph
4. Check for conditional logic preventing update
## Editor Issues
### Preview Not Loading
**Symptom**: Preview panel is blank or shows error
**Solutions**:
1. Check browser console for errors
2. Verify viewer runtime is built
3. Check for JavaScript errors in project
4. Try creating a new empty project
### Property Panel Empty
**Symptom**: Selected node but no properties shown
**Solutions**:
1. Verify node has `inputs` defined
2. Check `group` values are set
3. Look for errors in property panel code
4. Verify node type is registered
### Canvas Performance Issues
**Symptom**: Node graph is slow/laggy
**Solutions**:
1. Reduce number of visible nodes
2. Check for expensive render operations
3. Verify no infinite update loops
4. Profile with Chrome DevTools
## Git Issues
### Merge Conflicts in package-lock.json
**Symptom**: Complex conflicts in lock file
**Solutions**:
1. Accept either version
2. Run `npm install` to regenerate
3. Commit the regenerated lock file
### Large File Warnings
**Symptom**: Git warns about large files
**Solutions**:
1. Check `.gitignore` includes build outputs
2. Verify `node_modules` not committed
3. Use Git LFS for large assets if needed
## Testing Issues
### Tests Timeout
**Symptom**: Tests hang or timeout
**Solutions**:
1. Check for unresolved promises
2. Verify mocks are set up correctly
3. Increase timeout if legitimately slow
4. Check for infinite loops
### Snapshot Tests Failing
**Symptom**: Snapshot doesn't match
**Solutions**:
1. Review the diff carefully
2. If change is intentional: `npm test -- -u`
3. If unexpected, investigate component changes
## Debugging Tips
### Enable Verbose Logging
```javascript
// Add to see more info
console.log('[DEBUG]', variable);
// For node execution
this.context.debugLog('Message', data);
```
### Use Chrome DevTools
1. Open editor
2. Press `Cmd+Option+I` (Mac) or `Ctrl+Shift+I` (Windows)
3. Check Console for errors
4. Use Sources for breakpoints
5. Use Network for API issues
### Inspect Node State
```javascript
// In browser console
const node = NoodlRuntime.instance.getNodeById('node-id');
console.log(node._internal);
```
### Check Event Flow
```javascript
// Add listener to see all events
model.on('*', (event, data) => {
console.log('Event:', event, data);
});
```
## Error Messages
### "Maximum call stack size exceeded"
**Cause**: Infinite recursion or circular dependency
**Fix**:
1. Check for circular imports
2. Add base case to recursive functions
3. Break dependency cycles
### "Cannot access before initialization"
**Cause**: Temporal dead zone with `let`/`const`
**Fix**:
1. Check import order
2. Move declaration before usage
3. Check for circular imports
### "Unexpected token"
**Cause**: Syntax error or wrong file type
**Fix**:
1. Check file extension matches content
2. Verify JSON is valid
3. Check for missing brackets/quotes
### "ENOENT: no such file or directory"
**Cause**: Missing file or wrong path
**Fix**:
1. Verify file exists
2. Check path is correct (case-sensitive)
3. Ensure build step completed
## Getting Help
1. Search this document first
2. Check existing task documentation
3. Search codebase for similar patterns
4. Check GitHub issues
5. Ask in community channels
## Contributing Solutions
Found a solution not listed here? Add it!
1. Edit this file
2. Follow the format: Symptom → Solutions
3. Include specific commands when helpful
4. Submit PR with your addition

View File

@@ -0,0 +1,446 @@
# Node Patterns Reference
How to create and modify nodes in OpenNoodl.
## Node Types
There are two main types of nodes:
1. **Runtime Nodes** (`noodl-runtime`) - Logic, data, utilities
2. **Visual Nodes** (`noodl-viewer-react`) - React components for UI
## Basic Node Structure
### Runtime Node (JavaScript)
Location: `packages/noodl-runtime/src/nodes/`
```javascript
'use strict';
const MyNode = {
// === METADATA ===
name: 'My.Custom.Node', // Unique identifier
displayName: 'My Custom Node', // Shown in UI
category: 'Custom', // Node picker category
color: 'data', // Node color theme
docs: 'https://docs.example.com', // Documentation link
// === INITIALIZATION ===
initialize() {
// Called when node is created
this._internal.myValue = '';
this._internal.callbacks = [];
},
// === INPUTS ===
inputs: {
// Simple input
textInput: {
type: 'string',
displayName: 'Text Input',
group: 'General',
default: '',
set(value) {
this._internal.textInput = value;
this.flagOutputDirty('result');
}
},
// Number with validation
numberInput: {
type: 'number',
displayName: 'Number',
group: 'General',
default: 0,
set(value) {
if (typeof value !== 'number') return;
this._internal.numberInput = value;
this.flagOutputDirty('result');
}
},
// Signal input (trigger)
doAction: {
type: 'signal',
displayName: 'Do Action',
group: 'Actions',
valueChangedToTrue() {
// Called when signal received
this.performAction();
}
},
// Boolean toggle
enabled: {
type: 'boolean',
displayName: 'Enabled',
group: 'General',
default: true,
set(value) {
this._internal.enabled = value;
}
},
// Dropdown/enum
mode: {
type: {
name: 'enum',
enums: [
{ value: 'mode1', label: 'Mode 1' },
{ value: 'mode2', label: 'Mode 2' }
]
},
displayName: 'Mode',
group: 'General',
default: 'mode1',
set(value) {
this._internal.mode = value;
}
}
},
// === OUTPUTS ===
outputs: {
// Value output
result: {
type: 'string',
displayName: 'Result',
group: 'General',
getter() {
return this._internal.result;
}
},
// Signal output
completed: {
type: 'signal',
displayName: 'Completed',
group: 'Events'
},
// Error output
error: {
type: 'string',
displayName: 'Error',
group: 'Error',
getter() {
return this._internal.error;
}
}
},
// === METHODS ===
methods: {
performAction() {
if (!this._internal.enabled) return;
try {
// Do something
this._internal.result = 'Success';
this.flagOutputDirty('result');
this.sendSignalOnOutput('completed');
} catch (e) {
this._internal.error = e.message;
this.flagOutputDirty('error');
}
},
// Called when node is deleted
_onNodeDeleted() {
// Cleanup
this._internal.callbacks = [];
}
},
// === INSPECTOR (Debug Panel) ===
getInspectInfo() {
return {
type: 'text',
value: `Current: ${this._internal.result}`
};
}
};
module.exports = {
node: MyNode
};
```
### Visual Node (React)
Location: `packages/noodl-viewer-react/src/nodes/`
```javascript
'use strict';
const { Node } = require('@noodl/noodl-runtime');
const MyVisualNode = {
name: 'My.Visual.Node',
displayName: 'My Visual Node',
category: 'UI Elements',
// Visual nodes need these
allowChildren: true, // Can have child nodes
allowChildrenWithCategory: ['UI Elements'], // Restrict child types
getReactComponent() {
return MyReactComponent;
},
// Frame updates for animations
frame: {
// Called every frame if registered
update(context) {
// Animation logic
}
},
inputs: {
// Standard style inputs
backgroundColor: {
type: 'color',
displayName: 'Background Color',
group: 'Style',
default: 'transparent',
set(value) {
this.props.style.backgroundColor = value;
this.forceUpdate();
}
},
// Dimension with units
width: {
type: {
name: 'number',
units: ['px', '%', 'vw'],
defaultUnit: 'px'
},
displayName: 'Width',
group: 'Dimensions',
set(value) {
this.props.style.width = value.value + value.unit;
this.forceUpdate();
}
}
},
outputs: {
// DOM event outputs
onClick: {
type: 'signal',
displayName: 'Click',
group: 'Events'
}
},
methods: {
// Called when mounted
didMount() {
// Setup
},
// Called when unmounted
willUnmount() {
// Cleanup
}
}
};
// React component
function MyReactComponent(props) {
const handleClick = () => {
props.noodlNode.sendSignalOnOutput('onClick');
};
return (
<div style={props.style} onClick={handleClick}>
{props.children}
</div>
);
}
module.exports = {
node: MyVisualNode
};
```
## Common Patterns
### Scheduled Updates
Batch multiple input changes before processing:
```javascript
inputs: {
value1: {
set(value) {
this._internal.value1 = value;
this.scheduleProcess();
}
},
value2: {
set(value) {
this._internal.value2 = value;
this.scheduleProcess();
}
}
},
methods: {
scheduleProcess() {
if (this._internal.scheduled) return;
this._internal.scheduled = true;
this.scheduleAfterInputsHaveUpdated(() => {
this._internal.scheduled = false;
this.processValues();
});
},
processValues() {
// Process both values together
}
}
```
### Async Operations
Handle promises and async work:
```javascript
inputs: {
fetch: {
type: 'signal',
valueChangedToTrue() {
this.doFetch();
}
}
},
methods: {
async doFetch() {
try {
const response = await fetch(this._internal.url);
const data = await response.json();
this._internal.result = data;
this.flagOutputDirty('result');
this.sendSignalOnOutput('success');
} catch (error) {
this._internal.error = error.message;
this.flagOutputDirty('error');
this.sendSignalOnOutput('failure');
}
}
}
```
### Collection/Model Binding
Work with Noodl's data system:
```javascript
const Collection = require('../../../collection');
const Model = require('../../../model');
inputs: {
items: {
type: 'array',
set(value) {
this.bindCollection(value);
}
}
},
methods: {
bindCollection(collection) {
// Unbind previous
if (this._internal.collection) {
this._internal.collection.off('change', this._internal.onChange);
}
this._internal.collection = collection;
if (collection) {
this._internal.onChange = () => {
this.flagOutputDirty('count');
};
collection.on('change', this._internal.onChange);
}
}
}
```
### Dynamic Ports
Add ports based on configuration:
```javascript
inputs: {
properties: {
type: { name: 'stringlist', allowEditOnly: true },
displayName: 'Properties',
set(value) {
// Register dynamic inputs/outputs based on list
value.forEach(prop => {
if (!this.hasInput('prop-' + prop)) {
this.registerInput('prop-' + prop, {
set(val) {
this._internal.values[prop] = val;
}
});
}
});
}
}
}
```
## Input Types Reference
| Type | Description | Example |
|------|-------------|---------|
| `string` | Text input | `type: 'string'` |
| `number` | Numeric input | `type: 'number'` |
| `boolean` | Toggle | `type: 'boolean'` |
| `color` | Color picker | `type: 'color'` |
| `signal` | Trigger/event | `type: 'signal'` |
| `array` | Array/collection | `type: 'array'` |
| `object` | Object/model | `type: 'object'` |
| `component` | Component reference | `type: 'component'` |
| `enum` | Dropdown selection | `type: { name: 'enum', enums: [...] }` |
| `stringlist` | Editable list | `type: { name: 'stringlist' }` |
| `number` with units | Dimension | `type: { name: 'number', units: [...] }` |
## Node Colors
Available color themes for nodes:
- `data` - Blue (data operations)
- `logic` - Purple (logic/control)
- `visual` - Green (UI elements)
- `component` - Orange (component utilities)
- `default` - Gray
## Registering Nodes
Add to the node library export:
```javascript
// In packages/noodl-runtime/src/nodelibraryexport.js
const MyNode = require('./nodes/my-node');
// Add to appropriate category in coreNodes array
```
## Testing Nodes
```javascript
// Example test structure
describe('MyNode', () => {
it('should process input correctly', () => {
const node = createNode('My.Custom.Node');
node.setInput('textInput', 'hello');
expect(node.getOutput('result')).toBe('HELLO');
});
});
```