# Project Learnings This document captures important discoveries and gotchas encountered during OpenNoodl development. --- ## ποΈ CRITICAL ARCHITECTURE PATTERNS These fundamental patterns apply across ALL Noodl development. Understanding them prevents hours of debugging. --- ## π΄ Editor/Runtime Window Separation (Jan 2026) ### The Invisible Boundary: Why Editor Methods Don't Exist in Runtime **Context**: TASK-012 Blockly Integration - Discovered that editor and runtime run in completely separate JavaScript contexts (different windows/iframes). This is THE most important architectural detail to understand. **CRITICAL PRINCIPLE**: The OpenNoodl editor and runtime are NOT in the same JavaScript execution context. They are separate windows that communicate via message passing. **What This Means**: ``` βββββββββββββββββββββββ βββββββββββββββββββββββ β Editor Window β Message β Runtime Window β β β Passing β β β - ProjectModel ββ-------ββ - Node execution β β - NodeGraphEditor β β - Dynamic ports β β - graphModel β β - Code compilation β β - UI components β β - No editor access! β βββββββββββββββββββββββ βββββββββββββββββββββββ ``` **The Broken Pattern**: ```javascript // β WRONG - In runtime node code, trying to access editor function updatePorts(nodeId, workspace, editorConnection) { // These look reasonable but FAIL silently or crash: const graphModel = getGraphModel(); // β οΈ Doesn't exist in runtime! const node = graphModel.getNodeWithId(nodeId); // β οΈ graphModel is undefined const code = node.parameters.generatedCode; // β οΈ Can't access node this way // Problem: Runtime has NO ACCESS to editor objects/methods } ``` **The Correct Pattern**: ```javascript // β RIGHT - Pass ALL data explicitly via parameters function updatePorts(nodeId, workspace, generatedCode, editorConnection) { // generatedCode passed directly - no cross-window access needed const detected = parseCode(generatedCode); editorConnection.sendDynamicPorts(nodeId, detected.ports); // All data provided explicitly through function parameters } // In editor: Pass the data explicitly when calling const node = graphModel.getNodeWithId(nodeId); updatePorts( node.id, node.parameters.workspace, node.parameters.generatedCode, // β Pass explicitly editorConnection ); ``` **Why This Matters**: - **Silent failures**: Attempting to access editor objects from runtime often fails silently - **Mysterious undefined errors**: "Cannot read property X of undefined" when objects don't exist - **Debugging nightmare**: Looks like your code is wrong when it's an architecture issue - **Affects ALL editor/runtime communication**: Dynamic ports, code generation, parameter updates **Common Mistakes**: 1. Looking up nodes in graphModel from runtime 2. Accessing ProjectModel from runtime 3. Trying to call editor methods from node setup functions 4. Assuming shared global scope between editor and runtime **Critical Rules**: 1. **NEVER** assume editor objects exist in runtime code 2. **ALWAYS** pass data explicitly through function parameters 3. **NEVER** look up nodes via graphModel from runtime 4. **ALWAYS** use event payloads with complete data 5. **TREAT** editor and runtime as separate processes that only communicate via messages **Applies To**: - Dynamic port detection systems - Code generation and compilation - Parameter updates and node configuration - Custom property editors - Any feature bridging editor and runtime **Detection**: - Runtime errors about undefined objects that "should exist" - Functions that work in editor but fail in runtime - Dynamic features that don't update when they should - Silent failures with no error messages **Time Saved**: Understanding this architectural boundary can save 2-4 hours PER feature that crosses the editor/runtime divide. **Location**: Discovered in TASK-012 Blockly Integration (Logic Builder dynamic ports) **Keywords**: editor runtime separation, window context, iframe, cross-context communication, graphModel, ProjectModel, dynamic ports, architecture boundary --- ## π‘ Dynamic Code Compilation Context (Jan 2026) ### The this Trap: Why new Function() + .call() Doesn't Work **Context**: TASK-012 Blockly Integration - Generated code failed with "ReferenceError: Outputs is not defined" despite context being passed via `.call()`. **CRITICAL PRINCIPLE**: When using `new Function()` to compile user code dynamically, execution context MUST be passed as function parameters, NOT via `this` or `.call()`. **The Problem**: Modern JavaScript scoping rules make `this` unreliable for providing execution context to dynamically compiled code. **The Broken Pattern**: ```javascript // β WRONG - Generated code can't access context variables const fn = new Function(code); // Code contains: Outputs["result"] = 'test'; fn.call(context); // context = { Outputs: {}, Inputs: {}, Noodl: {...} } // Result: ReferenceError: Outputs is not defined // Why: Generated code has no lexical access to context properties ``` **The Correct Pattern**: ```javascript // β RIGHT - Pass context as function parameters const fn = new Function( 'Inputs', // Parameter names define lexical scope 'Outputs', 'Noodl', 'Variables', 'Objects', 'Arrays', 'sendSignalOnOutput', code // Function body - can reference parameters by name ); // Call with actual values as arguments fn( context.Inputs, context.Outputs, context.Noodl, context.Variables, context.Objects, context.Arrays, context.sendSignalOnOutput ); // Generated code: Outputs["result"] = 'test'; // β Works! Outputs is in scope ``` **Why This Works**: Function parameters create a proper lexical scope where the generated code can access variables by their parameter names. This is how closures and scope work in JavaScript. **Code Generator Pattern**: ```javascript // When generating code, reference parameters directly javascriptGenerator.forBlock['set_output'] = function (block) { const name = block.getFieldValue('NAME'); const value = javascriptGenerator.valueToCode(block, 'VALUE', Order.ASSIGNMENT); // Generated code uses parameter name directly - no 'context.' prefix needed return `Outputs["${name}"] = ${value};\n`; }; // Result: Outputs["result"] = "hello"; // Parameter name, not property access ``` **Comparison with eval()** (Don't use eval, but this explains the difference): ```javascript // eval() has access to surrounding scope (dangerous!) const context = { Outputs: {} }; eval('Outputs["result"] = "test"'); // Works but unsafe // new Function() creates isolated scope (safe!) const fn = new Function('Outputs', 'Outputs["result"] = "test"'); fn(context.Outputs); // Safe and works ``` **Critical Rules**: 1. **ALWAYS** pass execution context as function parameters 2. **NEVER** rely on `this` or `.call()` for context in compiled code 3. **GENERATE** code that references parameters directly, not properties 4. **LIST** all context variables as function parameters 5. **PASS** arguments in same order as parameters **Applies To**: - Expression node evaluation - JavaScript Function node execution - Logic Builder block code generation - Any dynamic code compilation system - Script evaluation in custom nodes **Common Mistakes**: 1. Using `.call(context)` and expecting generated code to access context properties 2. Using `.apply(context, args)` but not listing context as parameters 3. Generating code with `context.Outputs` instead of just `Outputs` 4. Forgetting to pass an argument for every parameter **Detection**: - "ReferenceError: [variable] is not defined" when executing compiled code - Variables exist in context but code can't access them - `.call()` or `.apply()` used but doesn't provide access - Generated code works in eval() but not new Function() **Time Saved**: This pattern prevents 1-2 hours of debugging per dynamic code feature. The error message gives no clue that the problem is parameter passing. **Location**: Discovered in TASK-012 Blockly Integration (Logic Builder execution) **Keywords**: new Function, dynamic code, compilation, execution context, this, call, apply, parameters, lexical scope, ReferenceError, code generation --- ## π¨ React Overlay Z-Index Pattern (Jan 2026) ### The Invisible UI: Why React Overlays Disappear Behind Canvas **Context**: TASK-012 Blockly Integration - React tabs were invisible because canvas layers rendered on top. This is a universal problem when adding React to legacy canvas systems. **CRITICAL PRINCIPLE**: When overlaying React components on legacy HTML5 Canvas or jQuery systems, DOM order alone is INSUFFICIENT. You MUST set explicit `position` and `z-index`. **The Problem**: Absolute-positioned canvas layers render based on z-index, not DOM order. React overlays without explicit z-index appear behind the canvas. **The Broken Pattern**: ```html
Delete "{deletingFolder.name}"?