# Can Noodl Build the New Agentic Erleah? ## Strategic Analysis & Roadmap **Date:** December 30, 2025 **Author:** Strategic Analysis **Status:** Recommendation - YES, with Phase 3.5 additions --- ## Executive Summary **TL;DR:** Yes, Noodl CAN build the new agentic Erleah, but it requires adding a focused "Phase 3.5: Real-Time Agentic UI" series of nodes and features. This is actually a PERFECT test case for Noodl's capabilities and would result in features that benefit the entire Noodl ecosystem. **Key Insights:** 1. **Foundation is solid** - Phases 1 & 2 created a modern React 19 + TypeScript base 2. **Core patterns exist** - HTTP nodes, state management, and event systems are already there 3. **Missing pieces are specific** - SSE streams, optimistic updates, and action dispatching 4. **High ROI** - Building these features makes Noodl better for ALL modern web apps 5. **Validation opportunity** - If Noodl can build Erleah, it proves the platform's maturity --- ## Current Capabilities Assessment ### ✅ What Noodl ALREADY Has #### 1. Modern Foundation (Phase 1 Complete) - **React 19** in both editor and runtime - **TypeScript 5** with full type inference - **Modern tooling** - webpack 5, Storybook 8 - **Performance** - Build times improved, hot reload snappy #### 2. HTTP & API Integration (Phase 2 In Progress) ```javascript // Current HTTP Node capabilities: - ✅ GET/POST/PUT/DELETE/PATCH methods - ✅ Authentication presets (Bearer, Basic, API Key) - ✅ JSONPath response mapping - ✅ Header and query parameter management - ✅ Form data and URL-encoded bodies - ✅ Timeout configuration - ✅ Cancel requests ``` #### 3. State Management ```javascript - ✅ Variable nodes for local state - ✅ Object/Array manipulation nodes - ✅ Component Inputs/Outputs for prop drilling - ✅ Send Event/Receive Event for pub-sub - ✅ States node for state machines ``` #### 4. Visual Components ```javascript - ✅ Full React component library - ✅ Responsive breakpoints (planned in NODES-001) - ✅ Visual states (hover, pressed, disabled) - ✅ Conditional rendering - ✅ Repeater for dynamic lists ``` #### 5. Event System ```javascript - ✅ Signal-based event propagation - ✅ EventDispatcher for pub-sub patterns - ✅ Connection-based data flow - ✅ Debounce/Delay nodes for timing ``` ### ❌ What Noodl Is MISSING for Erleah #### 1. **Server-Sent Events (SSE) Support** **Current Gap:** HTTP node only does request-response, no streaming **Erleah Needs:** ```javascript // Chat messages streaming in real-time AI Agent: "I'm searching attendees..." [streaming] AI Agent: "Found 8 matches..." [streaming] AI Agent: "Adding to your plan..." [streaming] ``` **What's Required:** - SSE connection node - Stream parsing (JSON chunks) - Progressive message accumulation - Automatic reconnection on disconnect #### 2. **WebSocket Support** **Current Gap:** No WebSocket node exists **Erleah Needs:** ```javascript // Real-time bidirectional communication User → Backend: "Add this to timeline" Backend → User: "Timeline updated" [instant] Backend → User: "Connection request accepted" [push] ``` #### 3. **Optimistic UI Updates** **Current Gap:** No pattern for "update UI first, sync later" **Erleah Needs:** ```javascript // Click "Accept" → immediate UI feedback // Then backend call → roll back if it fails ``` **What's Required:** - Transaction/rollback state management - Pending state indicators - Error recovery patterns #### 4. **Action Dispatcher Pattern** **Current Gap:** No concept of backend-triggered UI actions **Erleah Needs:** ```javascript // Backend (AI Agent) sends: { type: "OPEN_VIEW", view: "agenda", id: "session-123" } // Frontend automatically navigates ``` **What's Required:** - Action queue/processor - UI action vocabulary - Safe execution sandbox #### 5. **State Synchronization Across Views** **Current Gap:** Component state is isolated, no global reactive store **Erleah Needs:** ```javascript // Chat sidebar updates → Timeline view updates // Timeline view updates → Parking Lot updates // All views stay in sync automatically ``` **What's Required:** - Global observable store (like Zustand) - Subscription mechanism - Selective re-rendering --- ## Gap Analysis: Erleah Requirements vs Noodl Capabilities ### Feature Comparison Matrix | Erleah Feature | Noodl Today | Gap Size | Effort to Add | |----------------|-------------|----------|---------------| | **Timeline View** | ✅ Repeater + Cards | None | 0 days | | **Chat Sidebar** | ✅ Components | None | 0 days | | **Parking Lot Sidebar** | ✅ Components | None | 0 days | | **Card Layouts** | ✅ Visual nodes | None | 0 days | | **HTTP API Calls** | ✅ HTTP Node | None | 0 days | | **Authentication** | ✅ Auth presets | None | 0 days | | **SSE Streaming** | ❌ None | Large | 3-5 days | | **WebSocket** | ❌ None | Large | 3-5 days | | **Optimistic Updates** | ❌ None | Medium | 2-3 days | | **Action Dispatcher** | ⚠️ Partial | Medium | 2-4 days | | **Global State** | ⚠️ Workarounds | Small | 2-3 days | | **State History** | ❌ None | Small | 1-2 days | | **Real-time Preview** | ✅ Existing | None | 0 days | **Total New Development:** ~15-24 days --- ## Proposed Phase 3.5: Real-Time Agentic UI Insert this between current Phase 3 and the rest of the roadmap. ### Task Series: AGENT (AI Agent Integration) **Total Estimated:** 15-24 days (3-4 weeks) | Task ID | Name | Estimate | Description | |---------|------|----------|-------------| | **AGENT-001** | Server-Sent Events Node | 3-5 days | SSE connection, streaming, auto-reconnect | | **AGENT-002** | WebSocket Node | 3-5 days | Bidirectional real-time communication | | **AGENT-003** | Global State Store | 2-3 days | Observable store like Zustand, cross-component | | **AGENT-004** | Optimistic Update Pattern | 2-3 days | Transaction wrapper, rollback support | | **AGENT-005** | Action Dispatcher | 2-4 days | Backend-to-frontend command execution | | **AGENT-006** | State History & Time Travel | 1-2 days | Undo/redo, state snapshots | | **AGENT-007** | Stream Parser Utilities | 2-3 days | JSON streaming, chunk assembly | ### AGENT-001: Server-Sent Events Node **File:** `packages/noodl-runtime/src/nodes/std-library/data/ssenode.js` ```javascript var SSENode = { name: 'net.noodl.SSE', displayNodeName: 'Server-Sent Events', docs: 'https://docs.noodl.net/nodes/data/sse', category: 'Data', color: 'data', searchTags: ['sse', 'stream', 'server-sent', 'events', 'realtime'], initialize: function() { this._internal.eventSource = null; this._internal.isConnected = false; this._internal.messageBuffer = []; }, inputs: { url: { type: 'string', displayName: 'URL', group: 'Connection', set: function(value) { this._internal.url = value; } }, connect: { type: 'signal', displayName: 'Connect', group: 'Actions', valueChangedToTrue: function() { this.doConnect(); } }, disconnect: { type: 'signal', displayName: 'Disconnect', group: 'Actions', valueChangedToTrue: function() { this.doDisconnect(); } }, autoReconnect: { type: 'boolean', displayName: 'Auto Reconnect', group: 'Connection', default: true }, reconnectDelay: { type: 'number', displayName: 'Reconnect Delay (ms)', group: 'Connection', default: 3000 } }, outputs: { message: { type: 'object', displayName: 'Message', group: 'Data' }, data: { type: '*', displayName: 'Parsed Data', group: 'Data' }, connected: { type: 'signal', displayName: 'Connected', group: 'Events' }, disconnected: { type: 'signal', displayName: 'Disconnected', group: 'Events' }, error: { type: 'string', displayName: 'Error', group: 'Events' }, isConnected: { type: 'boolean', displayName: 'Is Connected', group: 'Status', getter: function() { return this._internal.isConnected; } } }, methods: { doConnect: function() { if (this._internal.eventSource) { this.doDisconnect(); } const url = this._internal.url; if (!url) { this.setError('URL is required'); return; } try { const eventSource = new EventSource(url); this._internal.eventSource = eventSource; eventSource.onopen = () => { this._internal.isConnected = true; this.flagOutputDirty('isConnected'); this.sendSignalOnOutput('connected'); }; eventSource.onmessage = (event) => { this.handleMessage(event); }; eventSource.onerror = (error) => { this._internal.isConnected = false; this.flagOutputDirty('isConnected'); this.sendSignalOnOutput('disconnected'); if (this._internal.autoReconnect) { setTimeout(() => { if (!this._internal.eventSource || this._internal.eventSource.readyState === EventSource.CLOSED) { this.doConnect(); } }, this._internal.reconnectDelay || 3000); } }; } catch (e) { this.setError(e.message); } }, doDisconnect: function() { if (this._internal.eventSource) { this._internal.eventSource.close(); this._internal.eventSource = null; this._internal.isConnected = false; this.flagOutputDirty('isConnected'); this.sendSignalOnOutput('disconnected'); } }, handleMessage: function(event) { try { // Try to parse as JSON const data = JSON.parse(event.data); this._internal.message = event; this._internal.data = data; } catch (e) { // Not JSON, use raw data this._internal.message = event; this._internal.data = event.data; } this.flagOutputDirty('message'); this.flagOutputDirty('data'); }, setError: function(message) { this._internal.error = message; this.flagOutputDirty('error'); }, _onNodeDeleted: function() { this.doDisconnect(); } } }; module.exports = { node: SSENode }; ``` ### AGENT-003: Global State Store Node **File:** `packages/noodl-runtime/src/nodes/std-library/data/globalstorenode.js` ```javascript // Global store instance (singleton) class GlobalStore { constructor() { this.stores = new Map(); this.subscribers = new Map(); } createStore(name, initialState = {}) { if (!this.stores.has(name)) { this.stores.set(name, initialState); this.subscribers.set(name, new Set()); } return this.stores.get(name); } getState(name) { return this.stores.get(name) || {}; } setState(name, updates) { const current = this.stores.get(name) || {}; const next = { ...current, ...updates }; this.stores.set(name, next); this.notify(name, next); } subscribe(name, callback) { if (!this.subscribers.has(name)) { this.subscribers.set(name, new Set()); } this.subscribers.get(name).add(callback); // Return unsubscribe function return () => { this.subscribers.get(name).delete(callback); }; } notify(name, state) { const subscribers = this.subscribers.get(name); if (subscribers) { subscribers.forEach(cb => cb(state)); } } } const globalStoreInstance = new GlobalStore(); var GlobalStoreNode = { name: 'net.noodl.GlobalStore', displayNodeName: 'Global Store', category: 'Data', color: 'data', initialize: function() { this._internal.storeName = 'default'; this._internal.unsubscribe = null; }, inputs: { storeName: { type: 'string', displayName: 'Store Name', default: 'default', set: function(value) { if (this._internal.unsubscribe) { this._internal.unsubscribe(); } this._internal.storeName = value; this.setupSubscription(); } }, set: { type: 'signal', displayName: 'Set', valueChangedToTrue: function() { this.doSet(); } }, key: { type: 'string', displayName: 'Key' }, value: { type: '*', displayName: 'Value' } }, outputs: { state: { type: 'object', displayName: 'State', getter: function() { return globalStoreInstance.getState(this._internal.storeName); } }, stateChanged: { type: 'signal', displayName: 'State Changed' } }, methods: { setupSubscription: function() { const storeName = this._internal.storeName; this._internal.unsubscribe = globalStoreInstance.subscribe( storeName, (newState) => { this.flagOutputDirty('state'); this.sendSignalOnOutput('stateChanged'); } ); // Trigger initial state this.flagOutputDirty('state'); }, doSet: function() { const key = this._internal.key; const value = this._internal.value; const storeName = this._internal.storeName; if (key) { globalStoreInstance.setState(storeName, { [key]: value }); } }, _onNodeDeleted: function() { if (this._internal.unsubscribe) { this._internal.unsubscribe(); } } } }; ``` ### AGENT-005: Action Dispatcher Node **File:** `packages/noodl-runtime/src/nodes/std-library/data/actiondispatchernode.js` ```javascript var ActionDispatcherNode = { name: 'net.noodl.ActionDispatcher', displayNodeName: 'Action Dispatcher', category: 'Events', color: 'purple', initialize: function() { this._internal.actionHandlers = new Map(); this._internal.pendingActions = []; }, inputs: { action: { type: 'object', displayName: 'Action', set: function(value) { this._internal.currentAction = value; this.dispatch(value); } }, // Register handlers registerHandler: { type: 'signal', displayName: 'Register Handler', valueChangedToTrue: function() { this.doRegisterHandler(); } }, handlerType: { type: 'string', displayName: 'Handler Type' }, handlerCallback: { type: 'signal', displayName: 'Handler Callback' } }, outputs: { actionType: { type: 'string', displayName: 'Action Type' }, actionData: { type: 'object', displayName: 'Action Data' }, dispatched: { type: 'signal', displayName: 'Dispatched' } }, methods: { dispatch: function(action) { if (!action || !action.type) return; this._internal.actionType = action.type; this._internal.actionData = action.data || {}; this.flagOutputDirty('actionType'); this.flagOutputDirty('actionData'); this.sendSignalOnOutput('dispatched'); // Execute registered handlers const handler = this._internal.actionHandlers.get(action.type); if (handler) { handler(action.data); } }, doRegisterHandler: function() { const type = this._internal.handlerType; if (!type) return; this._internal.actionHandlers.set(type, (data) => { this._internal.actionData = data; this.flagOutputDirty('actionData'); this.sendSignalOnOutput('handlerCallback'); }); } } }; ``` --- ## Implementation Strategy: Phases 1-2-3.5-3-4-5 ### Revised Roadmap ``` Phase 1: Foundation ✅ COMPLETE ├─ React 19 migration ├─ TypeScript 5 upgrade └─ Storybook 8 migration Phase 2: Core Features ⚙️ IN PROGRESS ├─ HTTP Node improvements ✅ COMPLETE ├─ Responsive breakpoints 🔄 ACTIVE ├─ Component migrations 🔄 ACTIVE └─ EventDispatcher React bridge ⚠️ BLOCKED Phase 3.5: Real-Time Agentic UI 🆕 PROPOSED ├─ AGENT-001: SSE Node (3-5 days) ├─ AGENT-002: WebSocket Node (3-5 days) ├─ AGENT-003: Global State Store (2-3 days) ├─ AGENT-004: Optimistic Updates (2-3 days) ├─ AGENT-005: Action Dispatcher (2-4 days) ├─ AGENT-006: State History (1-2 days) └─ AGENT-007: Stream Utilities (2-3 days) Total: 15-24 days (3-4 weeks) Phase 3: Advanced Features ├─ Dashboard UX (DASH series) ├─ Git Integration (GIT series) ├─ Shared Components (COMP series) ├─ AI Features (AI series) └─ Deployment (DEPLOY series) ``` ### Critical Path for Erleah To build Erleah, this is the minimum required path: **Week 1-2: Phase 3.5 Core** - AGENT-001 (SSE) - Absolutely critical for streaming AI responses - AGENT-003 (Global Store) - Required for synchronized state - AGENT-007 (Stream Utils) - Need to parse SSE JSON chunks **Week 3: Phase 3.5 Enhancement** - AGENT-004 (Optimistic Updates) - Better UX for user interactions - AGENT-005 (Action Dispatcher) - AI agent can control UI **Week 4: Erleah Development** - Build Timeline view - Build Chat sidebar with SSE - Build Parking Lot sidebar - Connect to backend **Total:** 4 weeks to validated Erleah prototype in Noodl --- ## Why This Is GOOD for Noodl ### 1. **Validates Modern Architecture** Building a complex, agentic UI proves that Noodl's React 19 + TypeScript migration was worth it. This is a real-world stress test. ### 2. **Features Benefit Everyone** SSE, WebSocket, and Global Store aren't "Erleah-specific" - every modern web app needs these: - Chat applications - Real-time dashboards - Collaborative tools - Live notifications - Streaming data visualization ### 3. **Competitive Advantage** Flutterflow, Bubble, Webflow - none have agentic UI patterns built in. This would be a differentiator. ### 4. **Dogfooding** Using Noodl to build a complex AI-powered app exposes UX issues and missing features that users face daily. ### 5. **Marketing Asset** "Built with Noodl" becomes a powerful case study. Erleah is a sophisticated, modern web app that competes with pure-code solutions. --- ## Risks & Mitigations ### Risk 1: "We're adding too much complexity" **Mitigation:** Phase 3.5 features are optional. Existing Noodl projects continue working. These are additive, not disruptive. ### Risk 2: "What if we hit a fundamental limitation?" **Mitigation:** Start with Phase 3.5 AGENT-001 (SSE) as a proof-of-concept. If that works smoothly, continue. If it's a nightmare, reconsider. ### Risk 3: "We're delaying Phase 3 features" **Mitigation:** Phase 3.5 is only 3-4 weeks. The learnings will inform Phase 3 (especially AI-001 AI Project Scaffolding). ### Risk 4: "SSE/WebSocket are complex to implement correctly" **Mitigation:** Leverage existing libraries (EventSource is native, WebSocket is native). Focus on the Noodl integration layer, not reinventing protocols. --- ## Alternative: Hybrid Approach If pure Noodl feels too risky, consider: ### Option A: Noodl Editor + React Runtime - Build most of Erleah in Noodl - Write 1-2 custom React components for SSE streaming in pure code - Import as "Custom React Components" (already supported in Noodl) **Pros:** - Faster initial development - No waiting for Phase 3.5 - Still validates Noodl for 90% of the app **Cons:** - Doesn't push Noodl forward - Misses opportunity to build reusable features ### Option B: Erleah 1.0 in Code, Erleah 2.0 in Noodl - Ship current Erleah version in pure React - Use learnings to design Phase 3.5 - Rebuild Erleah 2.0 in Noodl with Phase 3.5 features **Pros:** - No business risk - Informs Phase 3.5 design with real requirements - Validates Noodl with second implementation **Cons:** - Slower validation loop - Two separate codebases to maintain initially --- ## Recommendation ### ✅ Go Forward with Phase 3.5 **Rationale:** 1. **Timing is right** - Phases 1 & 2 created the foundation 2. **Scope is focused** - 7 tasks, 3-4 weeks, clear boundaries 3. **Value is high** - Erleah validates Noodl, features benefit everyone 4. **Risk is manageable** - Start with AGENT-001, can pivot if needed ### 📋 Action Plan **Immediate (Next 2 weeks):** 1. Create AGENT-001 (SSE Node) task document 2. Implement SSE Node as proof-of-concept 3. Build simple streaming chat UI in Noodl to test 4. Evaluate: Did this feel natural? Were there blockers? **If POC succeeds (Week 3-4):** 5. Complete AGENT-003 (Global Store) 6. Complete AGENT-007 (Stream Utils) 7. Build Erleah Timeline prototype in Noodl **If POC struggles:** 8. Document specific pain points 9. Consider hybrid approach 10. Inform future node design ### 🎯 Success Criteria Phase 3.5 is successful if: - ✅ SSE Node can stream AI responses smoothly - ✅ Global Store keeps views synchronized - ✅ Building Erleah in Noodl feels productive, not painful - ✅ The resulting app performs well (no visible lag) - ✅ Code is maintainable (not a tangled node spaghetti) --- ## Conclusion **Can Noodl build the new agentic Erleah?** Yes - but only with Phase 3.5 additions. Without SSE, Global Store, and Action Dispatcher patterns, you'd be fighting the platform. With them, Noodl becomes a powerful tool for building modern, reactive, AI-powered web apps. **Should you do it?** Yes - this is a perfect validation moment. You've invested heavily in modernizing Noodl. Now prove it can build something cutting-edge. If Noodl struggles with Erleah, that's valuable feedback. If it succeeds, you have a compelling case study and a suite of new features. **Timeline:** - **Phase 3.5 Development:** 3-4 weeks - **Erleah Prototype:** 1-2 weeks - **Total to Validation:** 4-6 weeks This is a strategic investment that pays dividends beyond just Erleah. --- ## Next Steps 1. **Review this document** with the team 2. **Decide on approach**: Full Phase 3.5, Hybrid, or Pure Code 3. **If Phase 3.5**: Start with AGENT-001 task creation 4. **If Hybrid**: Design the boundary between Noodl and custom React 5. **If Pure Code**: Document learnings for future Noodl improvements **Question to answer:** What would prove to you that Noodl CAN'T build Erleah? Define failure criteria upfront so you can pivot quickly if needed.