mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-13 07:42:55 +01:00
- Install blockly package (~500KB) - Create BlocklyWorkspace React component with serialization - Define custom Noodl blocks (Input/Output, Variables, Objects, Arrays) - Implement JavaScript code generators for all custom blocks - Add theme-aware styling for Blockly workspace - Export initialization functions for easy integration Part of TASK-012: Blockly Visual Logic Integration
422 lines
12 KiB
Markdown
422 lines
12 KiB
Markdown
# SUBTASK-B: Debug & Fix Expression/Function Node Outputs
|
|
|
|
**Parent Task:** TASK-008
|
|
**Status:** 🔴 Not Started
|
|
**Priority:** CRITICAL
|
|
**Estimated Effort:** 3-5 hours
|
|
|
|
---
|
|
|
|
## Problem
|
|
|
|
Expression and Function nodes evaluate/run but don't send output data to connected downstream nodes, breaking core functionality.
|
|
|
|
---
|
|
|
|
## Affected Nodes
|
|
|
|
1. **Expression Node** (`packages/noodl-runtime/src/nodes/std-library/expression.js`)
|
|
2. **Function Node** (`packages/noodl-runtime/src/nodes/std-library/simplejavascript.js`)
|
|
|
|
Both nodes share similar output mechanisms, suggesting a common underlying issue.
|
|
|
|
---
|
|
|
|
## Investigation Strategy
|
|
|
|
This is a **debugging task** - the root cause is unknown. We'll use systematic investigation to narrow down the issue.
|
|
|
|
### Phase 1: Minimal Reproduction 🔍
|
|
|
|
Create the simplest possible test case:
|
|
|
|
1. **Expression Node Test:**
|
|
|
|
- Create Expression node with `1 + 1`
|
|
- Connect output to Text node
|
|
- Expected: Text shows "2"
|
|
- Actual: Text shows nothing or old value
|
|
|
|
2. **Function Node Test:**
|
|
- Create Function node with `Outputs.result = 42;`
|
|
- Connect output to Text node
|
|
- Expected: Text shows "42"
|
|
- Actual: Text shows nothing or old value
|
|
|
|
**Document:**
|
|
|
|
- Exact steps to reproduce
|
|
- Screenshots of node graph
|
|
- Console output
|
|
- Any error messages
|
|
|
|
---
|
|
|
|
### Phase 2: Add Debug Logging 🔬
|
|
|
|
Add strategic console.log statements to trace execution flow.
|
|
|
|
#### Expression Node Logging
|
|
|
|
**File:** `packages/noodl-runtime/src/nodes/std-library/expression.js`
|
|
|
|
**Location 1 - Input Change:**
|
|
|
|
```javascript
|
|
// Line ~50, in expression input set()
|
|
set: function (value) {
|
|
console.log('🟢 [Expression] Input changed:', value);
|
|
var internal = this._internal;
|
|
internal.currentExpression = functionPreamble + 'return (' + value + ');';
|
|
// ... rest of code
|
|
if (!this.isInputConnected('run')) this._scheduleEvaluateExpression();
|
|
}
|
|
```
|
|
|
|
**Location 2 - Schedule:**
|
|
|
|
```javascript
|
|
// Line ~220, _scheduleEvaluateExpression
|
|
_scheduleEvaluateExpression: {
|
|
value: function () {
|
|
console.log('🔵 [Expression] Schedule evaluation called');
|
|
var internal = this._internal;
|
|
if (internal.hasScheduledEvaluation === false) {
|
|
console.log('🔵 [Expression] Scheduling callback');
|
|
internal.hasScheduledEvaluation = true;
|
|
this.flagDirty();
|
|
this.scheduleAfterInputsHaveUpdated(function () {
|
|
console.log('🔥 [Expression] Callback FIRED');
|
|
var lastValue = internal.cachedValue;
|
|
internal.cachedValue = this._calculateExpression();
|
|
console.log('🔥 [Expression] Calculated:', internal.cachedValue, 'Previous:', lastValue);
|
|
if (lastValue !== internal.cachedValue) {
|
|
console.log('🚩 [Expression] Flagging outputs dirty');
|
|
this.flagOutputDirty('result');
|
|
this.flagOutputDirty('isTrue');
|
|
this.flagOutputDirty('isFalse');
|
|
}
|
|
if (internal.cachedValue) this.sendSignalOnOutput('isTrueEv');
|
|
else this.sendSignalOnOutput('isFalseEv');
|
|
internal.hasScheduledEvaluation = false;
|
|
});
|
|
} else {
|
|
console.log('⚠️ [Expression] Already scheduled, skipping');
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Location 3 - Output Getter:**
|
|
|
|
```javascript
|
|
// Line ~145, result output getter
|
|
result: {
|
|
group: 'Result',
|
|
type: '*',
|
|
displayName: 'Result',
|
|
getter: function () {
|
|
console.log('📤 [Expression] Result getter called, returning:', this._internal.cachedValue);
|
|
if (!this._internal.currentExpression) {
|
|
return 0;
|
|
}
|
|
return this._internal.cachedValue;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Function Node Logging
|
|
|
|
**File:** `packages/noodl-runtime/src/nodes/std-library/simplejavascript.js`
|
|
|
|
**Location 1 - Schedule:**
|
|
|
|
```javascript
|
|
// Line ~100, scheduleRun method
|
|
scheduleRun: function () {
|
|
console.log('🔵 [Function] Schedule run called');
|
|
if (this.runScheduled) {
|
|
console.log('⚠️ [Function] Already scheduled, skipping');
|
|
return;
|
|
}
|
|
this.runScheduled = true;
|
|
|
|
this.scheduleAfterInputsHaveUpdated(() => {
|
|
console.log('🔥 [Function] Callback FIRED');
|
|
this.runScheduled = false;
|
|
|
|
if (!this._deleted) {
|
|
this.runScript();
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
**Location 2 - Proxy:**
|
|
|
|
```javascript
|
|
// Line ~25, Proxy set trap
|
|
this._internal.outputValuesProxy = new Proxy(this._internal.outputValues, {
|
|
set: (obj, prop, value) => {
|
|
console.log('🔵 [Function] Proxy intercepted:', prop, '=', value);
|
|
//a function node can continue running after it has been deleted. E.g. with timeouts or event listeners that hasn't been removed.
|
|
//if the node is deleted, just do nothing
|
|
if (this._deleted) {
|
|
console.log('⚠️ [Function] Node deleted, ignoring output');
|
|
return;
|
|
}
|
|
|
|
//only send outputs when they change.
|
|
//Some Noodl projects rely on this behavior, so changing it breaks backwards compability
|
|
if (value !== this._internal.outputValues[prop]) {
|
|
console.log('🚩 [Function] Flagging output dirty:', 'out-' + prop);
|
|
this.registerOutputIfNeeded('out-' + prop);
|
|
|
|
this._internal.outputValues[prop] = value;
|
|
this.flagOutputDirty('out-' + prop);
|
|
} else {
|
|
console.log('⏭️ [Function] Output unchanged, skipping');
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
```
|
|
|
|
**Location 3 - Output Getter:**
|
|
|
|
```javascript
|
|
// Line ~185, getScriptOutputValue method
|
|
getScriptOutputValue: function (name) {
|
|
console.log('📤 [Function] Output getter called:', name, 'value:', this._internal.outputValues[name]);
|
|
if (this._isSignalType(name)) {
|
|
return undefined;
|
|
}
|
|
return this._internal.outputValues[name];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 3: Test with Logging 📊
|
|
|
|
1. Add all debug logging above
|
|
2. Run `npm run dev` to start editor
|
|
3. Create test nodes (Expression and Function)
|
|
4. Watch console output
|
|
5. Document what logs appear and what logs are missing
|
|
|
|
**Expected Log Flow (Expression):**
|
|
|
|
```
|
|
🟢 [Expression] Input changed: 1 + 1
|
|
🔵 [Expression] Schedule evaluation called
|
|
🔵 [Expression] Scheduling callback
|
|
🔥 [Expression] Callback FIRED
|
|
🔥 [Expression] Calculated: 2 Previous: 0
|
|
🚩 [Expression] Flagging outputs dirty
|
|
📤 [Expression] Result getter called, returning: 2
|
|
```
|
|
|
|
**Expected Log Flow (Function):**
|
|
|
|
```
|
|
🔵 [Function] Schedule run called
|
|
🔥 [Function] Callback FIRED
|
|
🔵 [Function] Proxy intercepted: result = 42
|
|
🚩 [Function] Flagging output dirty: out-result
|
|
📤 [Function] Output getter called: result value: 42
|
|
```
|
|
|
|
**If logs stop at certain point, that's where the bug is.**
|
|
|
|
---
|
|
|
|
### Phase 4: Narrow Down Root Cause 🎯
|
|
|
|
Based on Phase 3 findings, investigate specific areas:
|
|
|
|
#### Scenario A: Callback Never Fires
|
|
|
|
**Symptoms:**
|
|
|
|
- See "Schedule" logs but never see "Callback FIRED"
|
|
- `scheduleAfterInputsHaveUpdated()` not working
|
|
|
|
**Investigation:**
|
|
|
|
- Check `packages/noodl-runtime/src/node.js` - `scheduleAfterInputsHaveUpdated` implementation
|
|
- Verify `this._afterInputsHaveUpdatedCallbacks` array exists
|
|
- Check if `_performDirtyUpdate` is being called
|
|
- Look for React 19 related changes that might have broken scheduling
|
|
|
|
**Potential Fix:**
|
|
|
|
- Fix scheduling mechanism
|
|
- Ensure callbacks are executed properly
|
|
|
|
#### Scenario B: Outputs Flagged But Getters Not Called
|
|
|
|
**Symptoms:**
|
|
|
|
- See "Flagging outputs dirty" logs
|
|
- Never see "Output getter called" logs
|
|
- `flagOutputDirty()` works but doesn't trigger downstream updates
|
|
|
|
**Investigation:**
|
|
|
|
- Check base `Node` class `flagOutputDirty()` implementation
|
|
- Verify downstream nodes are checking for dirty outputs
|
|
- Check if connection system is broken
|
|
- Look for changes to output propagation mechanism
|
|
|
|
**Potential Fix:**
|
|
|
|
- Fix output propagation system
|
|
- Ensure getters are called when outputs are dirty
|
|
|
|
#### Scenario C: Context/Scope Missing
|
|
|
|
**Symptoms:**
|
|
|
|
- Expression compilation fails silently
|
|
- No errors in console but calculation returns 0 or undefined
|
|
|
|
**Investigation:**
|
|
|
|
- Add logging to check `this.context`
|
|
- Add logging to check `this.context.modelScope`
|
|
- Verify Noodl globals (Variables, Objects, Arrays) are accessible
|
|
|
|
**Potential Fix:**
|
|
|
|
- Ensure context is properly initialized
|
|
- Fix scope setup
|
|
|
|
#### Scenario D: Proxy Not Working (Function Only)
|
|
|
|
**Symptoms:**
|
|
|
|
- Function runs but Proxy set trap never fires
|
|
- Output assignments don't trigger updates
|
|
|
|
**Investigation:**
|
|
|
|
- Test if Proxy works in current Node.js version
|
|
- Check if `this._internal` exists when Proxy is created
|
|
- Verify Proxy is being used (not bypassed)
|
|
|
|
**Potential Fix:**
|
|
|
|
- Fix Proxy initialization
|
|
- Use alternative output mechanism if Proxy is broken
|
|
|
|
---
|
|
|
|
### Phase 5: Implement Fix 🔧
|
|
|
|
Once root cause is identified:
|
|
|
|
1. Implement targeted fix
|
|
2. Remove debug logging (or make conditional)
|
|
3. Test thoroughly
|
|
4. Document fix in INVESTIGATION.md
|
|
5. Add entry to LEARNINGS.md
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
- [ ] Expression nodes output correct values to connected nodes
|
|
- [ ] Function nodes output correct values to connected nodes
|
|
- [ ] Signal outputs work correctly
|
|
- [ ] Reactive updates work (expression updates when inputs change)
|
|
- [ ] No console errors during evaluation
|
|
- [ ] Downstream nodes receive and display outputs
|
|
- [ ] Existing projects using these nodes work correctly
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
### Expression Node Tests
|
|
|
|
- [ ] Simple math: `1 + 1` outputs `2`
|
|
- [ ] With inputs: Connect Number node to `x`, expression `x * 2` outputs correct value
|
|
- [ ] With signals: Connect Run signal, expression evaluates on trigger
|
|
- [ ] With Noodl globals: `Variables.myVar` outputs correct value
|
|
- [ ] Signal outputs: `isTrueEv` fires when result is truthy
|
|
- [ ] Multiple connected outputs: Both `result` and `asString` work
|
|
|
|
### Function Node Tests
|
|
|
|
- [ ] Simple output: `Outputs.result = 42` outputs `42`
|
|
- [ ] Multiple outputs: Multiple `Outputs.x = ...` assignments all work
|
|
- [ ] Signal outputs: `Outputs.done.send()` triggers correctly
|
|
- [ ] With inputs: Access `Inputs.x` and output based on it
|
|
- [ ] Async functions: `async` functions work correctly
|
|
- [ ] Error handling: Errors don't crash editor, show in warnings
|
|
|
|
### Integration Tests
|
|
|
|
- [ ] Chain: Expression → Function → Text all work
|
|
- [ ] Multiple connections: One output connected to multiple inputs
|
|
- [ ] Reactive updates: Changing upstream input updates downstream
|
|
- [ ] Component boundary: Nodes work inside components
|
|
|
|
---
|
|
|
|
## Related Files
|
|
|
|
**Core:**
|
|
|
|
- `packages/noodl-runtime/src/node.js` - Base Node class
|
|
- `packages/noodl-runtime/src/nodecontext.js` - Node context/scope
|
|
- `packages/noodl-runtime/src/nodes/std-library/expression.js` - Expression node
|
|
- `packages/noodl-runtime/src/nodes/std-library/simplejavascript.js` - Function node
|
|
|
|
**Related Systems:**
|
|
|
|
- `packages/noodl-runtime/src/expression-evaluator.js` - Expression evaluation
|
|
- `packages/noodl-runtime/src/outputproperty.js` - Output handling
|
|
- `packages/noodl-runtime/src/nodegraphcontext.js` - Graph-level context
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
- This is investigation-heavy - expect to spend time debugging
|
|
- Root cause may affect other node types
|
|
- May uncover deeper runtime issues
|
|
- Document all findings thoroughly
|
|
- Consider adding automated tests for these nodes once fixed
|
|
- If fix is complex, consider creating separate LEARNINGS entry
|
|
|
|
---
|
|
|
|
## Debugging Tips
|
|
|
|
**If stuck:**
|
|
|
|
1. Compare with a known-working node type (e.g., Number node)
|
|
2. Check git history for recent changes to affected files
|
|
3. Test in older version to see if regression
|
|
4. Ask Richard about recent runtime changes
|
|
5. Check if similar issues reported in GitHub issues
|
|
|
|
**Useful console commands:**
|
|
|
|
```javascript
|
|
// Get node instance
|
|
const node = window.Noodl.Graphs['Component'].nodes[0];
|
|
|
|
// Check outputs
|
|
node._internal.cachedValue;
|
|
node._internal.outputValues;
|
|
|
|
// Test flagging manually
|
|
node.flagOutputDirty('result');
|
|
|
|
// Check scheduling
|
|
node._afterInputsHaveUpdatedCallbacks;
|
|
```
|