From 147451f94030cd47a24821015f12cde967bb2305 Mon Sep 17 00:00:00 2001
From: YurenHao0426 <blackhao0426@gmail.com>
Date: Sat, 14 Feb 2026 05:07:13 +0000
Subject: Create quick chat nodes immediately on send, fix Enter key routing

- Move node creation decision before API call so new nodes appear
  instantly with loading state instead of waiting for full response
- Use useFlowStore.getState().nodes for fresh state in the decision
- Fix Enter key in prompt textarea to call handleRunCouncil/handleRunDebate
  for council/debate nodes instead of always calling handleRun

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 frontend/src/components/Sidebar.tsx | 457 +++++++++++++++---------------------
 1 file changed, 183 insertions(+), 274 deletions(-)

(limited to 'frontend/src/components/Sidebar.tsx')

diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
index 08f0988..bf0363d 100644
--- a/frontend/src/components/Sidebar.tsx
+++ b/frontend/src/components/Sidebar.tsx
@@ -1334,7 +1334,7 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
     const userInput = quickChatInput;
     const attachedFilesCopy = [...quickChatAttachedFiles];
     const msgId = `qc_${Date.now()}_u`;
-    
+
     const userMessage: Message = {
       id: msgId,
       role: 'user',
@@ -1365,11 +1365,152 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
     const councilModelsAtSend = isCouncilSend ? [...quickChatCouncilModels] : [];
     const chairmanAtSend = isCouncilSend ? (quickChatChairmanModel || quickChatCouncilModels[0]) : null;
 
+    // ====== Determine target node BEFORE API call ======
+    // Create new node immediately so it appears on the blueprint with loading state
+    const fromNodeId = quickChatLastNodeId || quickChatTrace.sourceNodeId;
+    const freshNodes = useFlowStore.getState().nodes;
+    const fromNode = freshNodes.find(n => n.id === fromNodeId);
+    const fromNodeHasResponse = fromNode?.data.response && fromNode.data.response.trim() !== '';
+
+    let targetNodeId: string; // The node that will receive the response
+    let createdNewNode = false;
+
+    if (!fromNodeHasResponse && fromNode) {
+      // Overwrite the source node (it's empty) — set loading immediately
+      targetNodeId = fromNodeId;
+      const nodeUpdate: any = {
+        userPrompt: userInput,
+        model: isCouncilSend ? chairmanAtSend!.model : modelAtSend,
+        temperature: isCouncilSend ? selectedNode.data.temperature : (reasoningModels.includes(modelAtSend) ? 1 : tempAtSend),
+        reasoningEffort: isCouncilSend ? (selectedNode.data.reasoningEffort || 'medium') : effortAtSend,
+        enableGoogleSearch: isCouncilSend ? (selectedNode.data.enableGoogleSearch !== false) : webSearchAtSend,
+        attachedFileIds: attachedFilesCopy,
+        status: 'loading',
+        response: '',
+        querySentAt: Date.now(),
+      };
+      if (isCouncilSend) {
+        nodeUpdate.councilMode = true;
+        nodeUpdate.councilModels = councilModelsAtSend;
+        nodeUpdate.chairmanModel = chairmanAtSend;
+      }
+      updateNodeData(targetNodeId, nodeUpdate);
+      setQuickChatLastNodeId(fromNodeId);
+    } else {
+      // Create new node immediately with loading state
+      createdNewNode = true;
+      targetNodeId = `node_${Date.now()}`;
+      const sourceNode = fromNode || selectedNode;
+      const newPos = {
+        x: sourceNode.position.x + 300,
+        y: sourceNode.position.y
+      };
+
+      const newNodeData: any = {
+        label: isCouncilSend ? 'Council Chat' : 'Quick Chat',
+        model: isCouncilSend ? chairmanAtSend!.model : modelAtSend,
+        temperature: isCouncilSend ? selectedNode.data.temperature : (reasoningModels.includes(modelAtSend) ? 1 : tempAtSend),
+        systemPrompt: '',
+        userPrompt: userInput,
+        mergeStrategy: 'smart' as const,
+        reasoningEffort: isCouncilSend ? (selectedNode.data.reasoningEffort || 'medium') : effortAtSend,
+        enableGoogleSearch: isCouncilSend ? (selectedNode.data.enableGoogleSearch !== false) : webSearchAtSend,
+        traces: [],
+        outgoingTraces: [],
+        forkedTraces: [],
+        mergedTraces: [],
+        activeTraceIds: [],
+        attachedFileIds: attachedFilesCopy,
+        response: '',
+        status: 'loading' as const,
+        inputs: 1,
+        querySentAt: Date.now(),
+      };
+      if (isCouncilSend) {
+        newNodeData.councilMode = true;
+        newNodeData.councilModels = councilModelsAtSend;
+        newNodeData.chairmanModel = chairmanAtSend;
+      }
+
+      addNode({
+        id: targetNodeId,
+        type: 'llmNode',
+        position: newPos,
+        data: newNodeData,
+      });
+
+      // Connect edge immediately
+      setTimeout(() => {
+        const store = useFlowStore.getState();
+        const currentEdges = store.edges;
+        const sourceNodeData = store.nodes.find(n => n.id === fromNodeId);
+
+        let sourceHandle = 'new-trace';
+        const currentTraceId = quickChatTrace?.id || '';
+        const isNewTrace = currentTraceId.startsWith('new-trace-');
+        const isMergedTrace = currentTraceId.startsWith('merged-');
+
+        if (isMergedTrace) {
+          const evolvedMergedId = `${currentTraceId}_${fromNodeId}`;
+          let mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(t => t.id === evolvedMergedId);
+          if (!mergedOutgoing) mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(t => t.id.startsWith(currentTraceId) && t.id.endsWith(`_${fromNodeId}`));
+          if (!mergedOutgoing) mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(t => t.id.startsWith(currentTraceId) || t.id === currentTraceId);
+          if (!mergedOutgoing) mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(t => t.id.startsWith('merged-'));
+          sourceHandle = mergedOutgoing ? `trace-${mergedOutgoing.id}` : `trace-${currentTraceId}`;
+        } else if (isNewTrace) {
+          const originalStartNodeId = currentTraceId.replace('new-trace-', '');
+          const isOriginalNode = fromNodeId === originalStartNodeId;
+          if (isOriginalNode) {
+            const hasOutgoingEdges = currentEdges.some(e => e.source === fromNodeId);
+            if (hasOutgoingEdges) {
+              sourceHandle = 'new-trace';
+            } else {
+              const selfTrace = sourceNodeData?.data.outgoingTraces?.find(t => t.id === `trace-${fromNodeId}`);
+              if (selfTrace) sourceHandle = `trace-${selfTrace.id}`;
+            }
+          } else {
+            const matchingTrace = sourceNodeData?.data.outgoingTraces?.find(t => t.id.includes(originalStartNodeId));
+            if (matchingTrace) {
+              sourceHandle = `trace-${matchingTrace.id}`;
+            } else {
+              const incoming = sourceNodeData?.data.traces?.find(t => t.id.includes(originalStartNodeId));
+              if (incoming) sourceHandle = `trace-${incoming.id}`;
+              else {
+                const anyMatch = sourceNodeData?.data.outgoingTraces?.find(t => t.id === `trace-${fromNodeId}`);
+                if (anyMatch) sourceHandle = `trace-${anyMatch.id}`;
+              }
+            }
+          }
+        } else {
+          const baseTraceId = currentTraceId.replace(/^trace-/, '');
+          const matchingOutgoing = sourceNodeData?.data.outgoingTraces?.find(t => t.id.replace(/^trace-/, '') === baseTraceId);
+          if (matchingOutgoing) {
+            sourceHandle = `trace-${matchingOutgoing.id}`;
+          } else {
+            const matchingIncoming = sourceNodeData?.data.traces?.find(t => t.id.replace(/^trace-/, '') === baseTraceId);
+            if (matchingIncoming) sourceHandle = `trace-${matchingIncoming.id}`;
+          }
+        }
+
+        store.onConnect({
+          source: fromNodeId,
+          sourceHandle,
+          target: targetNodeId,
+          targetHandle: 'input-0'
+        });
+        setQuickChatNeedsDuplicate(false);
+      }, 50);
+
+      // Update quick chat trace to point to new node
+      const currentId = quickChatTrace?.id || '';
+      setQuickChatTrace(prev => prev ? { ...prev, sourceNodeId: targetNodeId } : null);
+      setQuickChatLastNodeId(targetNodeId);
+    }
+
+    // ====== Now make API call and stream into targetNodeId ======
     try {
-      // Build scopes for file search (Quick Chat uses a temp scope)
       const projectPath = currentBlueprintPath || 'untitled';
       const scopes = [`${projectPath}/quick_chat_temp`];
-
       let fullResponse = '';
 
       if (isCouncilSend && chairmanAtSend) {
@@ -1458,7 +1599,6 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
                 break;
               case 'stage3_chunk':
                 stage3Full += evt.data.chunk;
-                // Stream chairman response into chat
                 setQuickChatMessages(prev => {
                   const newMsgs = [...prev];
                   const lastMsg = newMsgs[newMsgs.length - 1];
@@ -1481,7 +1621,6 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
                 };
                 setQuickChatCouncilData(finalData);
                 setQuickChatCouncilStage('');
-                // Ensure final message is set
                 setQuickChatMessages(prev => {
                   const newMsgs = [...prev];
                   const lastMsg = newMsgs[newMsgs.length - 1];
@@ -1502,7 +1641,6 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
         fullResponse = stage3Full;
       } else {
         // ========== SINGLE MODEL MODE ==========
-        // Determine provider
         const isClaude = modelAtSend.includes('claude');
         const isOpenAI = modelAtSend.includes('gpt') || modelAtSend === 'o3';
         const provider = isClaude ? 'claude' : isOpenAI ? 'openai' : 'google';
@@ -1555,289 +1693,55 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
         }
       }
 
-      // Determine whether to overwrite current node or create new one
-      // Use quickChatLastNodeId as the "current" node in the chat flow to ensure continuity
-      // If not set, fallback to quickChatTrace.sourceNodeId (initial state)
-      const fromNodeId = quickChatLastNodeId || quickChatTrace.sourceNodeId;
-      const fromNode = nodes.find(n => n.id === fromNodeId);
-      const fromNodeHasResponse = fromNode?.data.response && fromNode.data.response.trim() !== '';
-      
-      if (!fromNodeHasResponse && fromNode) {
-        // Overwrite the source node (it's empty)
-        const nodeUpdate: any = {
-          userPrompt: userInput,
-          response: fullResponse,
-          model: isCouncilSend ? chairmanAtSend!.model : modelAtSend,
-          temperature: isCouncilSend ? selectedNode.data.temperature : (reasoningModels.includes(modelAtSend) ? 1 : tempAtSend),
-          reasoningEffort: isCouncilSend ? (selectedNode.data.reasoningEffort || 'medium') : effortAtSend,
-          enableGoogleSearch: isCouncilSend ? (selectedNode.data.enableGoogleSearch !== false) : webSearchAtSend,
-          attachedFileIds: attachedFilesCopy,
-          status: 'success',
-          querySentAt: Date.now(),
-          responseReceivedAt: Date.now(),
-        };
-        if (isCouncilSend) {
-          nodeUpdate.councilMode = true;
-          nodeUpdate.councilModels = councilModelsAtSend;
-          nodeUpdate.chairmanModel = chairmanAtSend;
-          nodeUpdate.councilData = quickChatCouncilData;
-        }
-        updateNodeData(fromNodeId, nodeUpdate);
-        
-        // Update trace to reflect current node now has content
-        setQuickChatTrace(prev => prev ? {
-          ...prev,
-          messages: [...messagesBeforeSend, userMessage, { id: `qc_${Date.now()}_a`, role: 'assistant', content: fullResponse }]
-        } : null);
-        
-        // Update last node ID
-        setQuickChatLastNodeId(fromNodeId);
-        
-        // Generate title
-        generateTitle(fromNodeId, userInput, fullResponse);
-      } else {
-        // Create new node (source node has response, continue the chain)
-        const newNodeId = `node_${Date.now()}`;
-        const sourceNode = fromNode || selectedNode;
-        const newPos = {
-          x: sourceNode.position.x + 300,
-          y: sourceNode.position.y
-        };
-        
-        const newNodeData: any = {
-            label: isCouncilSend ? 'Council Chat' : 'Quick Chat',
-            model: isCouncilSend ? chairmanAtSend!.model : modelAtSend,
-            temperature: isCouncilSend ? selectedNode.data.temperature : (reasoningModels.includes(modelAtSend) ? 1 : tempAtSend),
-            systemPrompt: '',
-            userPrompt: userInput,
-            mergeStrategy: 'smart' as const,
-            reasoningEffort: isCouncilSend ? (selectedNode.data.reasoningEffort || 'medium') : effortAtSend,
-            enableGoogleSearch: isCouncilSend ? (selectedNode.data.enableGoogleSearch !== false) : webSearchAtSend,
-            traces: [],
-            outgoingTraces: [],
-            forkedTraces: [],
-            mergedTraces: [],
-            activeTraceIds: [],
-            attachedFileIds: attachedFilesCopy,
-            response: fullResponse,
-            status: 'success' as const,
-            inputs: 1,
-            querySentAt: Date.now(),
-            responseReceivedAt: Date.now(),
-        };
-        if (isCouncilSend) {
-          newNodeData.councilMode = true;
-          newNodeData.councilModels = councilModelsAtSend;
-          newNodeData.chairmanModel = chairmanAtSend;
-          newNodeData.councilData = quickChatCouncilData;
-        }
-        const newNode = {
-          id: newNodeId,
-          type: 'llmNode',
-          position: newPos,
-          data: {
-            ...newNodeData,
-          }
-        };
+      // ====== Finalize: update target node with response ======
+      const finalUpdate: any = {
+        response: fullResponse,
+        status: 'success',
+        responseReceivedAt: Date.now(),
+      };
+      if (isCouncilSend) {
+        finalUpdate.councilData = quickChatCouncilData;
+      }
+      updateNodeData(targetNodeId, finalUpdate);
+
+      // Update trace messages to include final response
+      setQuickChatTrace(prev => prev ? {
+        ...prev,
+        messages: [...messagesBeforeSend, userMessage, { id: `qc_${Date.now()}_a`, role: 'assistant', content: fullResponse }]
+      } : null);
 
-        addNode(newNode);
+      // Generate title
+      generateTitle(targetNodeId, userInput, fullResponse);
 
-        // Connect to the source node
+      // For new nodes, update trace ID after onConnect has run
+      if (createdNewNode) {
         setTimeout(() => {
           const store = useFlowStore.getState();
-          const currentEdges = store.edges;
-          const sourceNodeData = store.nodes.find(n => n.id === fromNodeId);
-          
-          // Find the right trace handle to use
-          let sourceHandle = 'new-trace';
-          
-          // Get the base trace ID (e.g., 'trace-A' from 'trace-A_B_C' or 'new-trace-A' or 'merged-xxx')
-          const currentTraceId = quickChatTrace?.id || '';
-          const isNewTrace = currentTraceId.startsWith('new-trace-');
-          const isMergedTrace = currentTraceId.startsWith('merged-');
-          
-          if (isMergedTrace) {
-            // For merged trace: find the merged trace handle on the source node
-            // The trace ID may have evolved (e.g., 'merged-xxx' -> 'merged-xxx_nodeA' -> 'merged-xxx_nodeA_nodeB')
-            // We need to find the version that ends with the current source node ID
-            
-            // First try: exact match with evolved ID (merged-xxx_sourceNodeId)
-            const evolvedMergedId = `${currentTraceId}_${fromNodeId}`;
-            let mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(
-              t => t.id === evolvedMergedId
-            );
-            
-            // Second try: find trace that starts with merged ID and ends with this node
-            if (!mergedOutgoing) {
-              mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(
-                t => t.id.startsWith(currentTraceId) && t.id.endsWith(`_${fromNodeId}`)
-              );
-            }
-            
-            // Third try: find any trace that contains the merged ID
-            if (!mergedOutgoing) {
-              mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(
-                t => t.id.startsWith(currentTraceId) || t.id === currentTraceId
-              );
-            }
-            
-            // Fourth try: find any merged trace
-            if (!mergedOutgoing) {
-              mergedOutgoing = sourceNodeData?.data.outgoingTraces?.find(
-                t => t.id.startsWith('merged-')
-              );
-            }
-            
-            if (mergedOutgoing) {
-              sourceHandle = `trace-${mergedOutgoing.id}`;
-            } else {
-              // Last resort: use the merged trace ID directly
-              sourceHandle = `trace-${currentTraceId}`;
-            }
-          } else if (isNewTrace) {
-            // For "Start New Trace": create a fresh independent trace from the original node
-            // First, check if this is the original starting node or a continuation node
-            const originalStartNodeId = currentTraceId.replace('new-trace-', '');
-            const isOriginalNode = fromNodeId === originalStartNodeId;
-            
-            if (isOriginalNode) {
-              // This is the first round - starting from original node
-              const hasOutgoingEdges = currentEdges.some(e => e.source === fromNodeId);
-              if (hasOutgoingEdges) {
-                // Original node already has downstream - create a new fork
-                sourceHandle = 'new-trace';
-              } else {
-                // No downstream yet - use self trace
-                const selfTrace = sourceNodeData?.data.outgoingTraces?.find(
-                  t => t.id === `trace-${fromNodeId}`
-                );
-                if (selfTrace) {
-                  sourceHandle = `trace-${selfTrace.id}`;
-                }
-              }
-            } else {
-              // This is a continuation - find the trace ID (should be preserved now)
-              // Look for a trace that was created from the original node's self trace
-              const matchingTrace = sourceNodeData?.data.outgoingTraces?.find(t => {
-                return t.id.includes(originalStartNodeId);
-              });
-              
-              if (matchingTrace) {
-                sourceHandle = `trace-${matchingTrace.id}`;
-              } else {
-                // Fallback 1: Check INCOMING traces (Connect to Continue Handle)
-                const incoming = sourceNodeData?.data.traces?.find(t => 
-                  t.id.includes(originalStartNodeId)
-                );
-                
-                if (incoming) {
-                  // ID is preserved, so handle ID is just trace-{id}
-                  sourceHandle = `trace-${incoming.id}`;
-                } else {
-                  // Fallback 2: find any trace that ends with fromNodeId (unlikely if ID preserved)
-                  const anyMatch = sourceNodeData?.data.outgoingTraces?.find(
-                    t => t.id === `trace-${fromNodeId}`
-                  );
-                  if (anyMatch) {
-                    sourceHandle = `trace-${anyMatch.id}`;
-                  }
-                }
-              }
-            }
-          } else {
-            // For existing trace: ID is preserved
-            const baseTraceId = currentTraceId.replace(/^trace-/, '');
-            
-            // 1. Try OUTGOING traces first (if already connected downstream)
-            const matchingOutgoing = sourceNodeData?.data.outgoingTraces?.find(t => {
-              const traceBase = t.id.replace(/^trace-/, '');
-              return traceBase === baseTraceId; // Exact match now
-            });
-            
-            if (matchingOutgoing) {
-              sourceHandle = `trace-${matchingOutgoing.id}`;
-            } else {
-              // 2. Try INCOMING traces (Connect to Continue Handle)
-              const matchingIncoming = sourceNodeData?.data.traces?.find(t => {
-                 const tId = t.id.replace(/^trace-/, '');
-                 return tId === baseTraceId; // Exact match now
-              });
-              
-              if (matchingIncoming) {
-                 // ID is preserved
-                 sourceHandle = `trace-${matchingIncoming.id}`;
-              }
-            }
-          }
-          
-          // If this is the first message and we need to duplicate (has downstream),
-          // onConnect will automatically handle the trace duplication
-          // because the sourceHandle already has an outgoing edge
-          
-          store.onConnect({
-            source: fromNodeId,
-            sourceHandle,
-            target: newNodeId,
-            targetHandle: 'input-0'
-          });
-          
-          // After first duplication, subsequent messages continue on the new trace
-          // Reset the duplicate flag since we're now on the new branch
-          setQuickChatNeedsDuplicate(false);
-          
-          // Update trace for continued chat - use newNodeId as the new source
-          // Find the actual trace ID on the new node to ensure continuity
-          const newNode = store.nodes.find(n => n.id === newNodeId);
+          const newNode = store.nodes.find(n => n.id === targetNodeId);
           const currentId = quickChatTrace?.id || '';
           const isMerged = currentId.startsWith('merged-');
           const isCurrentNewTrace = currentId.startsWith('new-trace-');
-          
+
           let nextTraceId = currentId;
-          
           if (newNode && newNode.data.outgoingTraces) {
-            // Find the trace that continues the current conversation
-            // Now trace IDs don't evolve, so it should be simpler
-            
             if (isMerged) {
-               // Merged traces might still need evolution or logic check
-               // For now assuming linear extension keeps same ID if we changed flowStore
-               // But merged trace logic in flowStore might still append ID?
-               // Let's check if evolved version exists
-               const evolved = newNode.data.outgoingTraces.find(t => 
-                 t.id === `${currentId}_${newNodeId}`
-               );
-               if (evolved) nextTraceId = evolved.id;
-               else nextTraceId = currentId; // Try keeping same ID
+              const evolved = newNode.data.outgoingTraces.find(t => t.id === `${currentId}_${targetNodeId}`);
+              nextTraceId = evolved ? evolved.id : currentId;
             } else if (isCurrentNewTrace) {
-               // For new trace, check if we have an outgoing trace with the start node ID
-               const startNodeId = currentId.replace('new-trace-', '');
-               const match = newNode.data.outgoingTraces.find(t => 
-                 t.id.includes(startNodeId)
-               );
-               if (match) nextTraceId = match.id;
-            } else {
-               // Regular trace: ID should be preserved
-               nextTraceId = currentId;
+              const startNodeId = currentId.replace('new-trace-', '');
+              const match = newNode.data.outgoingTraces.find(t => t.id.includes(startNodeId));
+              if (match) nextTraceId = match.id;
             }
           }
-          
-          setQuickChatTrace(prev => prev ? {
-            ...prev,
-            id: nextTraceId,
-            sourceNodeId: newNodeId,
-            messages: [...messagesBeforeSend, userMessage, { id: `qc_${Date.now()}_a`, role: 'assistant', content: fullResponse }]
-          } : null);
-          
-          // Update last node ID to the new node
-          setQuickChatLastNodeId(newNodeId);
-          
-          // Generate title
-          generateTitle(newNodeId, userInput, fullResponse);
-        }, 100);
+
+          setQuickChatTrace(prev => prev ? { ...prev, id: nextTraceId } : null);
+        }, 200);
       }
 
     } catch (error) {
       console.error('Quick chat error:', error);
+      // Mark target node as error
+      updateNodeData(targetNodeId, { status: 'error' });
       setQuickChatMessages(prev => [...prev, {
         id: `qc_err_${Date.now()}`,
         role: 'assistant',
@@ -1847,7 +1751,6 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
     } finally {
       setQuickChatLoading(false);
       setQuickChatCouncilStage('');
-      // Refocus the input after sending
       setTimeout(() => {
         quickChatInputRef.current?.focus();
       }, 50);
@@ -2728,7 +2631,13 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onToggle, onInteract }) => {
                   if (e.key === 'Enter' && !e.shiftKey) {
                     e.preventDefault();
                     if (selectedNode.data.status !== 'loading' && activeTracesCheck.complete) {
-                      handleRun();
+                      if (selectedNode.data.councilMode && (selectedNode.data.councilModels || []).length >= 2) {
+                        handleRunCouncil();
+                      } else if (selectedNode.data.debateMode && (selectedNode.data.debateModels || []).length >= 2) {
+                        handleRunDebate();
+                      } else {
+                        handleRun();
+                      }
                     }
                   }
                   // Shift+Enter allows normal newline
-- 
cgit v1.2.3

