Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'forEach')
    at app.js:17:29

Что за ошибка?
document.addEventListener('DOMContentLoaded', () => {
    const workspace = document.getElementById('workspace');
    const nodesContainer = document.getElementById('nodes-container');
    const connectionsSvg = document.getElementById('connections-svg');
    const nodeList = document.getElementById('node-list');

    let draggingNode = null;
    let offsetX, offsetY;
    let connecting = false;
    let connectionSource = null;

    // Load node types
    fetch('/get_state')
        .then(res => res.json())
        .then(data => {
            // Populate node palette
            data.node_types.forEach(nodeType => {
                const nodeItem = document.createElement('div');
                nodeItem.className = 'node-item';
                nodeItem.textContent = nodeType;
                nodeItem.dataset.type = nodeType;
                nodeItem.addEventListener('mousedown', startDraggingNewNode);
                nodeList.appendChild(nodeItem);
            });

            // Load existing nodes and connections
            loadState(data);
        });

    function startDraggingNewNode(e) {
        const nodeType = e.target.dataset.type;
        const nodeId = 'node_' + Date.now();

        fetch('/create_node', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                type: nodeType,
                id: nodeId,
                pos: {x: e.clientX - 200, y: e.clientY}
            })
        })
        .then(res => res.json())
        .then(nodeData => {
            createNodeElement(nodeData);
        });
    }

    function createNodeElement(nodeData) {
        const nodeEl = document.createElement('div');
        nodeEl.className = 'node';
        nodeEl.id = nodeData.id;
        nodeEl.style.left = `${nodeData.pos.x}px`;
        nodeEl.style.top = `${nodeData.pos.y}px`;

        nodeEl.innerHTML = `
            <div class="node-header">${nodeData.properties.node_name || nodeData.type}</div>
            <div class="node-content">
                ${createInputsHTML(nodeData)}
                ${createPropertiesHTML(nodeData)}
                ${createOutputsHTML(nodeData)}
            </div>
        `;

        nodesContainer.appendChild(nodeEl);

        // Add event listeners
        nodeEl.querySelector('.node-header').addEventListener('mousedown', startDraggingNode);
        addSocketEventListeners(nodeEl, nodeData);
    }

    function createInputsHTML(nodeData) {
        return nodeData.inputs.map(input => `
            <div class="input">
                <div class="socket" data-socket="${input.name}" data-type="${input.type}" data-io="input"></div>
                <div class="label">${input.name}</div>
            </div>
        `).join('');
    }

    function createOutputsHTML(nodeData) {
        return nodeData.outputs.map(output => `
            <div class="output">
                <div class="label">${output.name}</div>
                <div class="socket" data-socket="${output.name}" data-type="${output.type}" data-io="output"></div>
            </div>
        `).join('');
    }

    function createPropertiesHTML(nodeData) {
        return Object.entries(nodeData.properties).map(([key, prop]) => {
            if (prop.type === 'number') {
                return `
                    <div class="property">
                        <label>${key}:
                            <input type="number" data-key="${key}"
                                   value="${nodeData.values[key] || prop.value}"
                                   min="${prop.min || ''}"
                                   max="${prop.max || ''}"
                                   class="property-input">
                        </label>
                    </div>
                `;
            } else {
                return `
                    <div class="property">
                        <label>${key}:
                            <input type="text" data-key="${key}"
                                   value="${nodeData.values[key] || prop.value}"
                                   class="property-input">
                        </label>
                    </div>
                `;
            }
        }).join('');
    }

    function addSocketEventListeners(nodeEl, nodeData) {
        nodeEl.querySelectorAll('.socket').forEach(socket => {
            socket.addEventListener('mousedown', startConnection);
        });

        nodeEl.querySelectorAll('.property-input').forEach(input => {
            input.addEventListener('change', updateNodeValue.bind(null, nodeData.id));
        });
    }

    function startDraggingNode(e) {
        draggingNode = e.target.closest('.node');
        offsetX = e.clientX - draggingNode.offsetLeft;
        offsetY = e.clientY - draggingNode.offsetTop;

        document.addEventListener('mousemove', dragNode);
        document.addEventListener('mouseup', stopDraggingNode);
    }

    function dragNode(e) {
        if (!draggingNode) return;

        const x = e.clientX - offsetX;
        const y = e.clientY - offsetY;

        draggingNode.style.left = `${x}px`;
        draggingNode.style.top = `${y}px`;

        // Update connections
        drawConnections();
    }

    function stopDraggingNode() {
        draggingNode = null;
        document.removeEventListener('mousemove', dragNode);
        document.removeEventListener('mouseup', stopDraggingNode);
    }

    function startConnection(e) {
        e.stopPropagation();
        connecting = true;

        const socket = e.target;
        const node = socket.closest('.node');

        connectionSource = {
            nodeId: node.id,
            socket: socket.dataset.socket,
            type: socket.dataset.type,
            io: socket.dataset.io,
            element: socket
        };

        document.addEventListener('mousemove', drawTempConnection);
        document.addEventListener('mouseup', finishConnection);
    }

    function drawTempConnection(e) {
        if (!connecting || !connectionSource) return;

        // Clear previous temp connection
        connectionsSvg.innerHTML = connectionsSvg.innerHTML.replace(/<path class="temp-connection".*?>/, '');

        const sourceRect = connectionSource.element.getBoundingClientRect();
        const sourceX = sourceRect.left + sourceRect.width/2 - workspace.getBoundingClientRect().left;
        const sourceY = sourceRect.top + sourceRect.height/2 - workspace.getBoundingClientRect().top;

        const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
        path.setAttribute('d', `M${sourceX},${sourceY} L${e.clientX - workspace.getBoundingClientRect().left},${e.clientY - workspace.getBoundingClientRect().top}`);
        path.setAttribute('stroke', '#88f');
        path.setAttribute('stroke-width', '2');
        path.setAttribute('fill', 'none');
        path.setAttribute('class', 'temp-connection');

        connectionsSvg.appendChild(path);
    }

    function finishConnection(e) {
        if (!connecting || !connectionSource) return;

        // Remove temp connection
        connectionsSvg.querySelector('.temp-connection')?.remove();

        const targetSocket = document.elementFromPoint(e.clientX, e.clientY)?.closest('.socket');
        if (!targetSocket) {
            resetConnection();
            return;
        }

        const targetNode = targetSocket.closest('.node');
        const target = {
            nodeId: targetNode.id,
            socket: targetSocket.dataset.socket,
            type: targetSocket.dataset.type,
            io: targetSocket.dataset.io,
            element: targetSocket
        };

        // Validate connection
        if (isValidConnection(connectionSource, target)) {
            createConnection(connectionSource, target);
        }

        resetConnection();
    }

    function isValidConnection(source, target) {
        return (
            source.nodeId !== target.nodeId &&
            source.io !== target.io &&
            source.type === target.type
        );
    }

    function createConnection(source, target) {
        // Send connection to server
        fetch('/connect', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                source_id: source.nodeId,
                source_slot: source.socket,
                target_id: target.nodeId,
                target_slot: target.socket
            })
        });

        // Draw connection
        drawConnections();
    }

    function drawConnections() {
        // Clear existing connections except temp ones
        connectionsSvg.querySelectorAll('path:not(.temp-connection)').forEach(el => el.remove());

        // Get all connections from server
        fetch('/get_state')
            .then(res => res.json())
            .then(data => {
                data.connections.forEach(conn => {
                    const sourceNode = document.getElementById(conn.source);
                    const targetNode = document.getElementById(conn.target);

                    if (sourceNode && targetNode) {
                        const sourceSocket = sourceNode.querySelector(`.socket[data-socket="${conn.source_slot}"][data-io="output"]`);
                        const targetSocket = targetNode.querySelector(`.socket[data-socket="${conn.target_slot}"][data-io="input"]`);

                        if (sourceSocket && targetSocket) {
                            const sourceRect = sourceSocket.getBoundingClientRect();
                            const targetRect = targetSocket.getBoundingClientRect();

                            const sourceX = sourceRect.left + sourceRect.width/2 - workspace.getBoundingClientRect().left;
                            const sourceY = sourceRect.top + sourceRect.height/2 - workspace.getBoundingClientRect().top;

                            const targetX = targetRect.left + targetRect.width/2 - workspace.getBoundingClientRect().left;
                            const targetY = targetRect.top + targetRect.height/2 - workspace.getBoundingClientRect().top;

                            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
                            path.setAttribute('d', `M${sourceX},${sourceY} C${sourceX + 100},${sourceY} ${targetX - 100},${targetY} ${targetX},${targetY}`);
                            path.setAttribute('stroke', '#88f');
                            path.setAttribute('stroke-width', '2');
                            path.setAttribute('fill', 'none');
                            path.setAttribute('class', 'connection');

                            connectionsSvg.appendChild(path);
                        }
                    }
                });
            });
    }

    function resetConnection() {
        connecting = false;
        connectionSource = null;
        document.removeEventListener('mousemove', drawTempConnection);
        document.removeEventListener('mouseup', finishConnection);
    }

    function updateNodeValue(nodeId, e) {
        const key = e.target.dataset.key;
        const value = e.target.value;

        fetch('/update_node', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({
                id: nodeId,
                values: {[key]: value}
            })
        });
    }

    function loadState(state) {
        // Create nodes
        Object.values(state.nodes).forEach(nodeData => {
            createNodeElement(nodeData);
        });

        // Draw connections
        drawConnections();
    }

    // Draw grid
    function drawGrid() {
        const canvas = document.getElementById('grid-canvas');
        const ctx = canvas.getContext('2d');
        const size = 20;

        function resizeCanvas() {
            canvas.width = workspace.clientWidth;
            canvas.height = workspace.clientHeight;
            redrawGrid();
        }

        function redrawGrid() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = '#333';
            ctx.lineWidth = 1;

            // Draw vertical lines
            for (let x = 0; x <= canvas.width; x += size) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, canvas.height);
                ctx.stroke();
            }

            // Draw horizontal lines
            for (let y = 0; y <= canvas.height; y += size) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(canvas.width, y);
                ctx.stroke();
            }
        }

        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
    }

    drawGrid();
});