Protokoll #11146

ID11146
Zeitstempel2025-12-23 16:40:04.955792
Clientroot
IP145.224.96.190
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens10,935 (Input: 2,637, Output: 8,298)
Dauer99 ms
Request-Zeit2025-12-23 16:40:04.955792
Response-Zeit2025-12-23 16:40:05.054553

Request

{
    "event": "PreToolUse",
    "tool_name": "Write",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/docs\/code-graph-global.php",
        "content": "<?php\n\ndeclare(strict_types=1);\n\n\/\/ @responsibility: View für globalen Projekt-Dependency-Graph\n\nob_start();\n?>\n<link rel=\"stylesheet\" href=\"\/css\/graph.css\">\n\n<nav class=\"breadcrumb\">\n    <a href=\"\/docs\/code\">Code-Analyse<\/a> &raquo;\n    <span>Projekt-Graph<\/span>\n<\/nav>\n\n<h1>Projekt Dependency Graph<\/h1>\n<p class=\"graph-stats\">Alle Dependency-Beziehungen im Projekt (nur interne Klassen)<\/p>\n\n<div class=\"graph-controls\">\n    <div class=\"graph-legend\">\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-class\"><\/span> Class<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-interface\"><\/span> Interface<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-trait\"><\/span> Trait<\/span>\n        <span class=\"graph-legend-item graph-legend-separator\"><span class=\"graph-legend-link graph-link-extends\"><\/span> extends<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-implements\"><\/span> implements<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-constructor\"><\/span> constructor<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-use\"><\/span> use<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-trait\"><\/span> trait<\/span>\n    <\/div>\n    <div class=\"graph-filters\">\n        <label for=\"dep-type-filter\" class=\"graph-filter-label\">Typ:<\/label>\n        <select id=\"dep-type-filter\" class=\"graph-filter-select\">\n            <option value=\"\">Alle<\/option>\n            <option value=\"extends\">extends<\/option>\n            <option value=\"implements\">implements<\/option>\n            <option value=\"constructor\">constructor<\/option>\n            <option value=\"use\">use<\/option>\n            <option value=\"trait\">trait<\/option>\n        <\/select>\n        <label for=\"namespace-filter\" class=\"graph-filter-label\">Namespace:<\/label>\n        <select id=\"namespace-filter\" class=\"graph-filter-select\">\n            <option value=\"\">Alle<\/option>\n        <\/select>\n        <button id=\"reset-zoom\" class=\"btn btn--secondary graph-reset-btn\">Reset Zoom<\/button>\n    <\/div>\n<\/div>\n\n<div id=\"graph-stats\" class=\"graph-stats\"><\/div>\n\n<div id=\"graph-container\" class=\"graph-container\"><\/div>\n\n<div class=\"graph-back-link\">\n    <a href=\"\/docs\/code\" class=\"btn btn--secondary\">&larr; Zurück zur Übersicht<\/a>\n<\/div>\n\n<script src=\"https:\/\/d3js.org\/d3.v7.min.js\"><\/script>\n<script>\n(function() {\n    const container = document.getElementById('graph-container');\n    const width = container.clientWidth;\n    const height = container.clientHeight;\n\n    const colors = {\n        class: '#6366f1',\n        interface: '#f59e0b',\n        trait: '#10b981'\n    };\n\n    const linkColors = {\n        extends: '#f59e0b',\n        implements: '#3b82f6',\n        constructor: '#8b5cf6',\n        use: '#94a3b8',\n        trait: '#14b8a6'\n    };\n\n    container.innerHTML = '<div class=\"graph-loading\">Lade Graph-Daten...<\/div>';\n\n    fetch('\/docs\/code\/graph-data')\n        .then(r => r.json())\n        .then(data => {\n            container.innerHTML = '';\n\n            document.getElementById('graph-stats').textContent =\n                `${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;\n\n            const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();\n            const filter = document.getElementById('namespace-filter');\n            namespaces.forEach(ns => {\n                const opt = document.createElement('option');\n                opt.value = ns;\n                opt.textContent = ns;\n                filter.appendChild(opt);\n            });\n\n            const namespaceGroups = {};\n            data.nodes.forEach((node, i) => {\n                const ns = node.namespace || 'global';\n                if (!namespaceGroups[ns]) namespaceGroups[ns] = [];\n                namespaceGroups[ns].push(node);\n            });\n\n            const nsKeys = Object.keys(namespaceGroups).sort();\n            const nsCount = nsKeys.length;\n            const centerX = width \/ 2;\n            const centerY = height \/ 2;\n            const nsRadius = Math.min(width, height) * 0.35;\n\n            nsKeys.forEach((ns, nsIndex) => {\n                const nodes = namespaceGroups[ns];\n                const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n                const nsCenterX = centerX + nsRadius * Math.cos(nsAngle);\n                const nsCenterY = centerY + nsRadius * Math.sin(nsAngle);\n\n                const nodeCount = nodes.length;\n                const nodeRadius = Math.min(80, 20 + nodeCount * 5);\n\n                nodes.forEach((node, nodeIndex) => {\n                    if (nodeCount === 1) {\n                        node.x = nsCenterX;\n                        node.y = nsCenterY;\n                    } else {\n                        const nodeAngle = (2 * Math.PI * nodeIndex) \/ nodeCount;\n                        node.x = nsCenterX + nodeRadius * Math.cos(nodeAngle);\n                        node.y = nsCenterY + nodeRadius * Math.sin(nodeAngle);\n                    }\n                });\n            });\n\n            const nodeIndex = {};\n            data.nodes.forEach((n, i) => nodeIndex[n.id] = i);\n\n            data.links.forEach(link => {\n                if (typeof link.source === 'number') {\n                    link.sourceNode = data.nodes[link.source];\n                    link.targetNode = data.nodes[link.target];\n                }\n            });\n\n            const svg = d3.select('#graph-container')\n                .append('svg')\n                .attr('width', width)\n                .attr('height', height);\n\n            const g = svg.append('g');\n\n            const zoom = d3.zoom()\n                .scaleExtent([0.1, 4])\n                .on('zoom', (event) => {\n                    g.attr('transform', event.transform);\n                });\n\n            svg.call(zoom);\n\n            document.getElementById('reset-zoom').addEventListener('click', () => {\n                svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);\n            });\n\n            svg.append('defs').selectAll('marker')\n                .data(['extends', 'implements', 'constructor', 'use', 'trait'])\n                .enter().append('marker')\n                .attr('id', d => 'arrow-global-' + d)\n                .attr('viewBox', '0 -5 10 10')\n                .attr('refX', 15)\n                .attr('refY', 0)\n                .attr('markerWidth', 5)\n                .attr('markerHeight', 5)\n                .attr('orient', 'auto')\n                .append('path')\n                .attr('d', 'M0,-5L10,0L0,5')\n                .attr('fill', d => linkColors[d]);\n\n            const nsLabels = g.append('g').attr('class', 'ns-labels');\n            nsKeys.forEach((ns, nsIndex) => {\n                const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n                const labelRadius = nsRadius + 100;\n                const labelX = centerX + labelRadius * Math.cos(nsAngle);\n                const labelY = centerY + labelRadius * Math.sin(nsAngle);\n\n                nsLabels.append('text')\n                    .attr('x', labelX)\n                    .attr('y', labelY)\n                    .attr('text-anchor', 'middle')\n                    .attr('fill', 'var(--text-muted)')\n                    .attr('font-size', '10px')\n                    .attr('font-weight', 'bold')\n                    .text(ns.split('\\\\').pop());\n            });\n\n            const link = g.append('g')\n                .selectAll('line')\n                .data(data.links)\n                .enter().append('line')\n                .attr('class', 'graph-link')\n                .attr('data-type', d => d.type)\n                .attr('stroke', d => linkColors[d.type] || '#94a3b8')\n                .attr('stroke-width', 1.5)\n                .attr('stroke-opacity', 0.4)\n                .attr('marker-end', d => 'url(#arrow-global-' + d.type + ')')\n                .attr('x1', d => data.nodes[d.source].x)\n                .attr('y1', d => data.nodes[d.source].y)\n                .attr('x2', d => data.nodes[d.target].x)\n                .attr('y2', d => data.nodes[d.target].y);\n\n            const node = g.append('g')\n                .selectAll('g')\n                .data(data.nodes)\n                .enter().append('g')\n                .attr('class', 'graph-node')\n                .attr('data-namespace', d => d.namespace)\n                .attr('transform', d => `translate(${d.x},${d.y})`)\n                .attr('cursor', d => d.fileId ? 'pointer' : 'default')\n                .on('click', (event, d) => {\n                    if (d.fileId) {\n                        window.location.href = '\/docs\/code\/' + d.fileId;\n                    }\n                });\n\n            node.append('circle')\n                .attr('r', 8)\n                .attr('fill', d => colors[d.type] || colors.class)\n                .attr('stroke', '#fff')\n                .attr('stroke-width', 1.5);\n\n            node.append('text')\n                .text(d => d.label)\n                .attr('x', 0)\n                .attr('y', 20)\n                .attr('text-anchor', 'middle')\n                .attr('fill', 'var(--text-primary)')\n                .attr('font-size', '8px');\n\n            node.append('title')\n                .text(d => d.id);\n\n            const depTypeFilter = document.getElementById('dep-type-filter');\n\n            function applyFilters() {\n                const selectedNs = filter.value;\n                const selectedType = depTypeFilter.value;\n\n                const connectedNodes = new Set();\n                if (selectedType) {\n                    data.links.forEach(link => {\n                        if (link.type === selectedType) {\n                            connectedNodes.add(data.nodes[link.source].id);\n                            connectedNodes.add(data.nodes[link.target].id);\n         ... [TRUNCATED-5807d5d2a59819a2]"
    }
}

Response

{
    "tool_response": {
        "type": "update",
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/View\/docs\/code-graph-global.php",
        "content": "<?php\n\ndeclare(strict_types=1);\n\n\/\/ @responsibility: View für globalen Projekt-Dependency-Graph\n\nob_start();\n?>\n<link rel=\"stylesheet\" href=\"\/css\/graph.css\">\n\n<nav class=\"breadcrumb\">\n    <a href=\"\/docs\/code\">Code-Analyse<\/a> &raquo;\n    <span>Projekt-Graph<\/span>\n<\/nav>\n\n<h1>Projekt Dependency Graph<\/h1>\n<p class=\"graph-stats\">Alle Dependency-Beziehungen im Projekt (nur interne Klassen)<\/p>\n\n<div class=\"graph-controls\">\n    <div class=\"graph-legend\">\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-class\"><\/span> Class<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-interface\"><\/span> Interface<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-trait\"><\/span> Trait<\/span>\n        <span class=\"graph-legend-item graph-legend-separator\"><span class=\"graph-legend-link graph-link-extends\"><\/span> extends<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-implements\"><\/span> implements<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-constructor\"><\/span> constructor<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-use\"><\/span> use<\/span>\n        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-trait\"><\/span> trait<\/span>\n    <\/div>\n    <div class=\"graph-filters\">\n        <label for=\"dep-type-filter\" class=\"graph-filter-label\">Typ:<\/label>\n        <select id=\"dep-type-filter\" class=\"graph-filter-select\">\n            <option value=\"\">Alle<\/option>\n            <option value=\"extends\">extends<\/option>\n            <option value=\"implements\">implements<\/option>\n            <option value=\"constructor\">constructor<\/option>\n            <option value=\"use\">use<\/option>\n            <option value=\"trait\">trait<\/option>\n        <\/select>\n        <label for=\"namespace-filter\" class=\"graph-filter-label\">Namespace:<\/label>\n        <select id=\"namespace-filter\" class=\"graph-filter-select\">\n            <option value=\"\">Alle<\/option>\n        <\/select>\n        <button id=\"reset-zoom\" class=\"btn btn--secondary graph-reset-btn\">Reset Zoom<\/button>\n    <\/div>\n<\/div>\n\n<div id=\"graph-stats\" class=\"graph-stats\"><\/div>\n\n<div id=\"graph-container\" class=\"graph-container\"><\/div>\n\n<div class=\"graph-back-link\">\n    <a href=\"\/docs\/code\" class=\"btn btn--secondary\">&larr; Zurück zur Übersicht<\/a>\n<\/div>\n\n<script src=\"https:\/\/d3js.org\/d3.v7.min.js\"><\/script>\n<script>\n(function() {\n    const container = document.getElementById('graph-container');\n    const width = container.clientWidth;\n    const height = container.clientHeight;\n\n    const colors = {\n        class: '#6366f1',\n        interface: '#f59e0b',\n        trait: '#10b981'\n    };\n\n    const linkColors = {\n        extends: '#f59e0b',\n        implements: '#3b82f6',\n        constructor: '#8b5cf6',\n        use: '#94a3b8',\n        trait: '#14b8a6'\n    };\n\n    container.innerHTML = '<div class=\"graph-loading\">Lade Graph-Daten...<\/div>';\n\n    fetch('\/docs\/code\/graph-data')\n        .then(r => r.json())\n        .then(data => {\n            container.innerHTML = '';\n\n            document.getElementById('graph-stats').textContent =\n                `${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;\n\n            const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();\n            const filter = document.getElementById('namespace-filter');\n            namespaces.forEach(ns => {\n                const opt = document.createElement('option');\n                opt.value = ns;\n                opt.textContent = ns;\n                filter.appendChild(opt);\n            });\n\n            const namespaceGroups = {};\n            data.nodes.forEach((node, i) => {\n                const ns = node.namespace || 'global';\n                if (!namespaceGroups[ns]) namespaceGroups[ns] = [];\n                namespaceGroups[ns].push(node);\n            });\n\n            const nsKeys = Object.keys(namespaceGroups).sort();\n            const nsCount = nsKeys.length;\n            const centerX = width \/ 2;\n            const centerY = height \/ 2;\n            const nsRadius = Math.min(width, height) * 0.35;\n\n            nsKeys.forEach((ns, nsIndex) => {\n                const nodes = namespaceGroups[ns];\n                const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n                const nsCenterX = centerX + nsRadius * Math.cos(nsAngle);\n                const nsCenterY = centerY + nsRadius * Math.sin(nsAngle);\n\n                const nodeCount = nodes.length;\n                const nodeRadius = Math.min(80, 20 + nodeCount * 5);\n\n                nodes.forEach((node, nodeIndex) => {\n                    if (nodeCount === 1) {\n                        node.x = nsCenterX;\n                        node.y = nsCenterY;\n                    } else {\n                        const nodeAngle = (2 * Math.PI * nodeIndex) \/ nodeCount;\n                        node.x = nsCenterX + nodeRadius * Math.cos(nodeAngle);\n                        node.y = nsCenterY + nodeRadius * Math.sin(nodeAngle);\n                    }\n                });\n            });\n\n            const nodeIndex = {};\n            data.nodes.forEach((n, i) => nodeIndex[n.id] = i);\n\n            data.links.forEach(link => {\n                if (typeof link.source === 'number') {\n                    link.sourceNode = data.nodes[link.source];\n                    link.targetNode = data.nodes[link.target];\n                }\n            });\n\n            const svg = d3.select('#graph-container')\n                .append('svg')\n                .attr('width', width)\n                .attr('height', height);\n\n            const g = svg.append('g');\n\n            const zoom = d3.zoom()\n                .scaleExtent([0.1, 4])\n                .on('zoom', (event) => {\n                    g.attr('transform', event.transform);\n                });\n\n            svg.call(zoom);\n\n            document.getElementById('reset-zoom').addEventListener('click', () => {\n                svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);\n            });\n\n            svg.append('defs').selectAll('marker')\n                .data(['extends', 'implements', 'constructor', 'use', 'trait'])\n                .enter().append('marker')\n                .attr('id', d => 'arrow-global-' + d)\n                .attr('viewBox', '0 -5 10 10')\n                .attr('refX', 15)\n                .attr('refY', 0)\n                .attr('markerWidth', 5)\n                .attr('markerHeight', 5)\n                .attr('orient', 'auto')\n                .append('path')\n                .attr('d', 'M0,-5L10,0L0,5')\n                .attr('fill', d => linkColors[d]);\n\n            const nsLabels = g.append('g').attr('class', 'ns-labels');\n            nsKeys.forEach((ns, nsIndex) => {\n                const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n                const labelRadius = nsRadius + 100;\n                const labelX = centerX + labelRadius * Math.cos(nsAngle);\n                const labelY = centerY + labelRadius * Math.sin(nsAngle);\n\n                nsLabels.append('text')\n                    .attr('x', labelX)\n                    .attr('y', labelY)\n                    .attr('text-anchor', 'middle')\n                    .attr('fill', 'var(--text-muted)')\n                    .attr('font-size', '10px')\n                    .attr('font-weight', 'bold')\n                    .text(ns.split('\\\\').pop());\n            });\n\n            const link = g.append('g')\n                .selectAll('line')\n                .data(data.links)\n                .enter().append('line')\n                .attr('class', 'graph-link')\n                .attr('data-type', d => d.type)\n                .attr('stroke', d => linkColors[d.type] || '#94a3b8')\n                .attr('stroke-width', 1.5)\n                .attr('stroke-opacity', 0.4)\n                .attr('marker-end', d => 'url(#arrow-global-' + d.type + ')')\n                .attr('x1', d => data.nodes[d.source].x)\n                .attr('y1', d => data.nodes[d.source].y)\n                .attr('x2', d => data.nodes[d.target].x)\n                .attr('y2', d => data.nodes[d.target].y);\n\n            const node = g.append('g')\n                .selectAll('g')\n                .data(data.nodes)\n                .enter().append('g')\n                .attr('class', 'graph-node')\n                .attr('data-namespace', d => d.namespace)\n                .attr('transform', d => `translate(${d.x},${d.y})`)\n                .attr('cursor', d => d.fileId ? 'pointer' : 'default')\n                .on('click', (event, d) => {\n                    if (d.fileId) {\n                        window.location.href = '\/docs\/code\/' + d.fileId;\n                    }\n                });\n\n            node.append('circle')\n                .attr('r', 8)\n                .attr('fill', d => colors[d.type] || colors.class)\n                .attr('stroke', '#fff')\n                .attr('stroke-width', 1.5);\n\n            node.append('text')\n                .text(d => d.label)\n                .attr('x', 0)\n                .attr('y', 20)\n                .attr('text-anchor', 'middle')\n                .attr('fill', 'var(--text-primary)')\n                .attr('font-size', '8px');\n\n            node.append('title')\n                .text(d => d.id);\n\n            const depTypeFilter = document.getElementById('dep-type-filter');\n\n            function applyFilters() {\n                const selectedNs = filter.value;\n                const selectedType = depTypeFilter.value;\n\n                const connectedNodes = new Set();\n                if (selectedType) {\n                    data.links.forEach(link => {\n                        if (link.type === selectedType) {\n                            connectedNodes.add(data.nodes[link.source].id);\n                            connectedNodes.add(data.nodes[link.target].id);\n         ... [TRUNCATED-5807d5d2a59819a2]",
        "structuredPatch": [
            {
                "oldStart": 6,
                "oldLines": 6,
                "newStart": 6,
                "newLines": 7,
                "lines": [
                    " ",
                    " ob_start();",
                    " ?>",
                    "+<link rel=\"stylesheet\" href=\"\/css\/graph.css\">",
                    " ",
                    " <nav class=\"breadcrumb\">",
                    "     <a href=\"\/docs\/code\">Code-Analyse<\/a> &raquo;"
                ]
            },
            {
                "oldStart": 13,
                "oldLines": 26,
                "newStart": 14,
                "newLines": 22,
                "lines": [
                    " <\/nav>",
                    " ",
                    " <h1>Projekt Dependency Graph<\/h1>",
                    "-<p style=\"color: var(--text-muted); margin-bottom: 1rem;\">",
                    "-    Alle Dependency-Beziehungen im Projekt (nur interne Klassen)",
                    "-<\/p>",
                    "+<p class=\"graph-stats\">Alle Dependency-Beziehungen im Projekt (nur interne Klassen)<\/p>",
                    " ",
                    "-<div class=\"graph-controls\" style=\"display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; align-items: center;\">",
                    "-    <div class=\"graph-legend\" style=\"display: flex; gap: 0.75rem; flex-wrap: wrap; font-size: 0.8rem;\">",
                    "-        <span><span style=\"display: inline-block; width: 10px; height: 10px; background: #6366f1; border-radius: 50%; margin-right: 0.2rem;\"><\/span> Class<\/span>",
                    "-        <span><span style=\"display: inline-block; width: 10px; height: 10px; background: #f59e0b; border-radius: 50%; margin-right: 0.2rem;\"><\/span> Interface<\/span>",
                    "-        <span><span style=\"display: inline-block; width: 10px; height: 10px; background: #10b981; border-radius: 50%; margin-right: 0.2rem;\"><\/span> Trait<\/span>",
                    "-        <span style=\"margin-left: 0.5rem; border-left: 1px solid var(--border-color); padding-left: 0.5rem;\">",
                    "-            <span style=\"display: inline-block; width: 16px; height: 2px; background: #f59e0b; margin-right: 0.2rem; vertical-align: middle;\"><\/span> extends",
                    "-        <\/span>",
                    "-        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #3b82f6; margin-right: 0.2rem; vertical-align: middle;\"><\/span> implements<\/span>",
                    "-        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #8b5cf6; margin-right: 0.2rem; vertical-align: middle;\"><\/span> constructor<\/span>",
                    "-        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #94a3b8; margin-right: 0.2rem; vertical-align: middle;\"><\/span> use<\/span>",
                    "-        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #14b8a6; margin-right: 0.2rem; vertical-align: middle;\"><\/span> trait<\/span>",
                    "+<div class=\"graph-controls\">",
                    "+    <div class=\"graph-legend\">",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-class\"><\/span> Class<\/span>",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-interface\"><\/span> Interface<\/span>",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-node graph-node-trait\"><\/span> Trait<\/span>",
                    "+        <span class=\"graph-legend-item graph-legend-separator\"><span class=\"graph-legend-link graph-link-extends\"><\/span> extends<\/span>",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-implements\"><\/span> implements<\/span>",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-constructor\"><\/span> constructor<\/span>",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-use\"><\/span> use<\/span>",
                    "+        <span class=\"graph-legend-item\"><span class=\"graph-legend-link graph-link-trait\"><\/span> trait<\/span>",
                    "     <\/div>",
                    "-    <div style=\"margin-left: auto; display: flex; gap: 0.5rem; align-items: center;\">",
                    "-        <label for=\"dep-type-filter\" style=\"font-size: 0.875rem;\">Typ:<\/label>",
                    "-        <select id=\"dep-type-filter\" style=\"padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);\">",
                    "+    <div class=\"graph-filters\">",
                    "+        <label for=\"dep-type-filter\" class=\"graph-filter-label\">Typ:<\/label>",
                    "+        <select id=\"dep-type-filter\" class=\"graph-filter-select\">",
                    "             <option value=\"\">Alle<\/option>",
                    "             <option value=\"extends\">extends<\/option>",
                    "             <option value=\"implements\">implements<\/option>"
                ]
            },
            {
                "oldStart": 40,
                "oldLines": 19,
                "newStart": 37,
                "newLines": 19,
                "lines": [
                    "             <option value=\"use\">use<\/option>",
                    "             <option value=\"trait\">trait<\/option>",
                    "         <\/select>",
                    "-        <label for=\"namespace-filter\" style=\"font-size: 0.875rem; margin-left: 0.5rem;\">Namespace:<\/label>",
                    "-        <select id=\"namespace-filter\" style=\"padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);\">",
                    "+        <label for=\"namespace-filter\" class=\"graph-filter-label\">Namespace:<\/label>",
                    "+        <select id=\"namespace-filter\" class=\"graph-filter-select\">",
                    "             <option value=\"\">Alle<\/option>",
                    "         <\/select>",
                    "-        <button id=\"reset-zoom\" class=\"btn btn--secondary btn--small\" style=\"padding: 0.25rem 0.5rem; font-size: 0.75rem;\">Reset Zoom<\/button>",
                    "+        <button id=\"reset-zoom\" class=\"btn btn--secondary graph-reset-btn\">Reset Zoom<\/button>",
                    "     <\/div>",
                    " <\/div>",
                    " ",
                    "-<div id=\"graph-stats\" style=\"font-size: 0.875rem; color: var(--text-muted); margin-bottom: 0.5rem;\"><\/div>",
                    "+<div id=\"graph-stats\" class=\"graph-stats\"><\/div>",
                    " ",
                    "-<div id=\"graph-container\" style=\"width: 100%; height: 700px; background: var(--bg-secondary); border-radius: 8px; overflow: hidden; position: relative;\"><\/div>",
                    "+<div id=\"graph-container\" class=\"graph-container\"><\/div>",
                    " ",
                    "-<div style=\"margin-top: 1.5rem;\">",
                    "+<div class=\"graph-back-link\">",
                    "     <a href=\"\/docs\/code\" class=\"btn btn--secondary\">&larr; Zurück zur Übersicht<\/a>",
                    " <\/div>",
                    " "
                ]
            },
            {
                "oldStart": 77,
                "oldLines": 19,
                "newStart": 74,
                "newLines": 16,
                "lines": [
                    "         trait: '#14b8a6'",
                    "     };",
                    " ",
                    "-    \/\/ Loading indicator",
                    "-    container.innerHTML = '<div style=\"display: flex; justify-content: center; align-items: center; height: 100%; color: var(--text-muted);\">Lade Graph-Daten...<\/div>';",
                    "+    container.innerHTML = '<div class=\"graph-loading\">Lade Graph-Daten...<\/div>';",
                    " ",
                    "     fetch('\/docs\/code\/graph-data')",
                    "         .then(r => r.json())",
                    "         .then(data => {",
                    "             container.innerHTML = '';",
                    " ",
                    "-            \/\/ Stats",
                    "             document.getElementById('graph-stats').textContent =",
                    "                 `${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;",
                    " ",
                    "-            \/\/ Populate namespace filter",
                    "             const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();",
                    "             const filter = document.getElementById('namespace-filter');",
                    "             namespaces.forEach(ns => {"
                ]
            },
            {
                "oldStart": 99,
                "oldLines": 7,
                "newStart": 93,
                "newLines": 6,
                "lines": [
                    "                 filter.appendChild(opt);",
                    "             });",
                    " ",
                    "-            \/\/ Group nodes by namespace for static layout",
                    "             const namespaceGroups = {};",
                    "             data.nodes.forEach((node, i) => {",
                    "                 const ns = node.namespace || 'global';"
                ]
            },
            {
                "oldStart": 107,
                "oldLines": 7,
                "newStart": 100,
                "newLines": 6,
                "lines": [
                    "                 namespaceGroups[ns].push(node);",
                    "             });",
                    " ",
                    "-            \/\/ Compute static positions (circular layout by namespace)",
                    "             const nsKeys = Object.keys(namespaceGroups).sort();",
                    "             const nsCount = nsKeys.length;",
                    "             const centerX = width \/ 2;"
                ]
            },
            {
                "oldStart": 135,
                "oldLines": 11,
                "newStart": 127,
                "newLines": 9,
                "lines": [
                    "                 });",
                    "             });",
                    " ",
                    "-            \/\/ Create node index for links",
                    "             const nodeIndex = {};",
                    "             data.nodes.forEach((n, i) => nodeIndex[n.id] = i);",
                    " ",
                    "-            \/\/ Resolve link indices",
                    "             data.links.forEach(link => {",
                    "                 if (typeof link.source === 'number') {",
                    "                     link.sourceNode = data.nodes[link.source];"
                ]
            },
            {
                "oldStart": 147,
                "oldLines": 7,
                "newStart": 137,
                "newLines": 6,
                "lines": [
                    "                 }",
                    "             });",
                    " ",
                    "-            \/\/ Create SVG with zoom",
                    "             const svg = d3.select('#graph-container')",
                    "                 .append('svg')",
                    "                 .attr('width', width)"
                ]
            },
            {
                "oldStart": 163,
                "oldLines": 12,
                "newStart": 152,
                "newLines": 10,
                "lines": [
                    " ",
                    "             svg.call(zoom);",
                    " ",
                    "-            \/\/ Reset zoom button",
                    "             document.getElementById('reset-zoom').addEventListener('click', () => {",
                    "                 svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);",
                    "             });",
                    " ",
                    "-            \/\/ Arrow markers",
                    "             svg.append('defs').selectAll('marker')",
                    "                 .data(['extends', 'implements', 'constructor', 'use', 'trait'])",
                    "                 .enter().append('marker')"
                ]
            },
            {
                "oldStart": 183,
                "oldLines": 7,
                "newStart": 170,
                "newLines": 6,
                "lines": [
                    "                 .attr('d', 'M0,-5L10,0L0,5')",
                    "                 .attr('fill', d => linkColors[d]);",
                    " ",
                    "-            \/\/ Draw namespace labels",
                    "             const nsLabels = g.append('g').attr('class', 'ns-labels');",
                    "             nsKeys.forEach((ns, nsIndex) => {",
                    "                 const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;"
                ]
            },
            {
                "oldStart": 201,
                "oldLines": 7,
                "newStart": 187,
                "newLines": 6,
                "lines": [
                    "                     .text(ns.split('\\\\').pop());",
                    "             });",
                    " ",
                    "-            \/\/ Draw links",
                    "             const link = g.append('g')",
                    "                 .selectAll('line')",
                    "                 .data(data.links)"
                ]
            },
            {
                "oldStart": 217,
                "oldLines": 7,
                "newStart": 202,
                "newLines": 6,
                "lines": [
                    "                 .attr('x2', d => data.nodes[d.target].x)",
                    "                 .attr('y2', d => data.nodes[d.target].y);",
                    " ",
                    "-            \/\/ Draw nodes",
                    "             const node = g.append('g')",
                    "                 .selectAll('g')",
                    "                 .data(data.nodes)"
                ]
            },
            {
                "oldStart": 249,
                "oldLines": 14,
                "newStart": 233,
                "newLines": 12,
                "lines": [
                    "             node.append('title')",
                    "                 .text(d => d.id);",
                    " ",
                    "-            \/\/ Combined filter function",
                    "             const depTypeFilter = document.getElementById('dep-type-filter');",
                    " ",
                    "             function applyFilters() {",
                    "                 const selectedNs = filter.value;",
                    "                 const selectedType = depTypeFilter.value;",
                    " ",
                    "-                \/\/ Find connected nodes for type filter",
                    "                 const connectedNodes = new Set();",
                    "                 if (selectedType) {",
                    "                     data.links.forEach(link => {"
                ]
            },
            {
                "oldStart": 267,
                "oldLines": 7,
                "newStart": 249,
                "newLines": 6,
                "lines": [
                    "                     });",
                    "                 }",
                    " ",
                    "-                \/\/ Filter links",
                    "                 d3.selectAll('.graph-link')",
                    "                     .style('opacity', d => {",
                    "                         const typeMatch = !selectedType || d.type === selectedType;"
                ]
            },
            {
                "oldStart": 280,
                "oldLines": 7,
                "newStart": 261,
                "newLines": 6,
                "lines": [
                    "                         return 0.6;",
                    "                     });",
                    " ",
                    "-                \/\/ Filter nodes",
                    "                 d3.selectAll('.graph-node')",
                    "                     .style('opacity', d => {",
                    "                         const typeMatch = !selectedType || connectedNodes.has(d.id);"
                ]
            },
            {
                "oldStart": 295,
                "oldLines": 7,
                "newStart": 275,
                "newLines": 6,
                "lines": [
                    "             filter.addEventListener('change', applyFilters);",
                    "             depTypeFilter.addEventListener('change', applyFilters);",
                    " ",
                    "-            \/\/ Initial zoom to fit",
                    "             const bounds = g.node().getBBox();",
                    "             const dx = bounds.width;",
                    "             const dy = bounds.height;"
                ]
            },
            {
                "oldStart": 309,
                "oldLines": 7,
                "newStart": 288,
                "newLines": 7,
                "lines": [
                    "                 .scale(scale));",
                    "         })",
                    "         .catch(err => {",
                    "-            container.innerHTML = '<p style=\"padding: 2rem; color: var(--text-danger);\">Fehler beim Laden des Graphen: ' + err.message + '<\/p>';",
                    "+            container.innerHTML = '<p class=\"graph-error\">Fehler beim Laden des Graphen: ' + err.message + '<\/p>';",
                    "         });",
                    " })();",
                    " <\/script>"
                ]
            }
        ],
        "originalFile": "<?php\n\ndeclare(strict_types=1);\n\n\/\/ @responsibility: View für globalen Projekt-Dependency-Graph\n\nob_start();\n?>\n\n<nav class=\"breadcrumb\">\n    <a href=\"\/docs\/code\">Code-Analyse<\/a> &raquo;\n    <span>Projekt-Graph<\/span>\n<\/nav>\n\n<h1>Projekt Dependency Graph<\/h1>\n<p style=\"color: var(--text-muted); margin-bottom: 1rem;\">\n    Alle Dependency-Beziehungen im Projekt (nur interne Klassen)\n<\/p>\n\n<div class=\"graph-controls\" style=\"display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; align-items: center;\">\n    <div class=\"graph-legend\" style=\"display: flex; gap: 0.75rem; flex-wrap: wrap; font-size: 0.8rem;\">\n        <span><span style=\"display: inline-block; width: 10px; height: 10px; background: #6366f1; border-radius: 50%; margin-right: 0.2rem;\"><\/span> Class<\/span>\n        <span><span style=\"display: inline-block; width: 10px; height: 10px; background: #f59e0b; border-radius: 50%; margin-right: 0.2rem;\"><\/span> Interface<\/span>\n        <span><span style=\"display: inline-block; width: 10px; height: 10px; background: #10b981; border-radius: 50%; margin-right: 0.2rem;\"><\/span> Trait<\/span>\n        <span style=\"margin-left: 0.5rem; border-left: 1px solid var(--border-color); padding-left: 0.5rem;\">\n            <span style=\"display: inline-block; width: 16px; height: 2px; background: #f59e0b; margin-right: 0.2rem; vertical-align: middle;\"><\/span> extends\n        <\/span>\n        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #3b82f6; margin-right: 0.2rem; vertical-align: middle;\"><\/span> implements<\/span>\n        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #8b5cf6; margin-right: 0.2rem; vertical-align: middle;\"><\/span> constructor<\/span>\n        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #94a3b8; margin-right: 0.2rem; vertical-align: middle;\"><\/span> use<\/span>\n        <span><span style=\"display: inline-block; width: 16px; height: 2px; background: #14b8a6; margin-right: 0.2rem; vertical-align: middle;\"><\/span> trait<\/span>\n    <\/div>\n    <div style=\"margin-left: auto; display: flex; gap: 0.5rem; align-items: center;\">\n        <label for=\"dep-type-filter\" style=\"font-size: 0.875rem;\">Typ:<\/label>\n        <select id=\"dep-type-filter\" style=\"padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);\">\n            <option value=\"\">Alle<\/option>\n            <option value=\"extends\">extends<\/option>\n            <option value=\"implements\">implements<\/option>\n            <option value=\"constructor\">constructor<\/option>\n            <option value=\"use\">use<\/option>\n            <option value=\"trait\">trait<\/option>\n        <\/select>\n        <label for=\"namespace-filter\" style=\"font-size: 0.875rem; margin-left: 0.5rem;\">Namespace:<\/label>\n        <select id=\"namespace-filter\" style=\"padding: 0.25rem 0.5rem; border-radius: 4px; border: 1px solid var(--border-color); background: var(--bg-secondary);\">\n            <option value=\"\">Alle<\/option>\n        <\/select>\n        <button id=\"reset-zoom\" class=\"btn btn--secondary btn--small\" style=\"padding: 0.25rem 0.5rem; font-size: 0.75rem;\">Reset Zoom<\/button>\n    <\/div>\n<\/div>\n\n<div id=\"graph-stats\" style=\"font-size: 0.875rem; color: var(--text-muted); margin-bottom: 0.5rem;\"><\/div>\n\n<div id=\"graph-container\" style=\"width: 100%; height: 700px; background: var(--bg-secondary); border-radius: 8px; overflow: hidden; position: relative;\"><\/div>\n\n<div style=\"margin-top: 1.5rem;\">\n    <a href=\"\/docs\/code\" class=\"btn btn--secondary\">&larr; Zurück zur Übersicht<\/a>\n<\/div>\n\n<script src=\"https:\/\/d3js.org\/d3.v7.min.js\"><\/script>\n<script>\n(function() {\n    const container = document.getElementById('graph-container');\n    const width = container.clientWidth;\n    const height = container.clientHeight;\n\n    const colors = {\n        class: '#6366f1',\n        interface: '#f59e0b',\n        trait: '#10b981'\n    };\n\n    const linkColors = {\n        extends: '#f59e0b',\n        implements: '#3b82f6',\n        constructor: '#8b5cf6',\n        use: '#94a3b8',\n        trait: '#14b8a6'\n    };\n\n    \/\/ Loading indicator\n    container.innerHTML = '<div style=\"display: flex; justify-content: center; align-items: center; height: 100%; color: var(--text-muted);\">Lade Graph-Daten...<\/div>';\n\n    fetch('\/docs\/code\/graph-data')\n        .then(r => r.json())\n        .then(data => {\n            container.innerHTML = '';\n\n            \/\/ Stats\n            document.getElementById('graph-stats').textContent =\n                `${data.stats.nodes} Klassen | ${data.stats.links} Beziehungen | ${data.stats.namespaces} Namespaces`;\n\n            \/\/ Populate namespace filter\n            const namespaces = [...new Set(data.nodes.map(n => n.namespace))].sort();\n            const filter = document.getElementById('namespace-filter');\n            namespaces.forEach(ns => {\n                const opt = document.createElement('option');\n                opt.value = ns;\n                opt.textContent = ns;\n                filter.appendChild(opt);\n            });\n\n            \/\/ Group nodes by namespace for static layout\n            const namespaceGroups = {};\n            data.nodes.forEach((node, i) => {\n                const ns = node.namespace || 'global';\n                if (!namespaceGroups[ns]) namespaceGroups[ns] = [];\n                namespaceGroups[ns].push(node);\n            });\n\n            \/\/ Compute static positions (circular layout by namespace)\n            const nsKeys = Object.keys(namespaceGroups).sort();\n            const nsCount = nsKeys.length;\n            const centerX = width \/ 2;\n            const centerY = height \/ 2;\n            const nsRadius = Math.min(width, height) * 0.35;\n\n            nsKeys.forEach((ns, nsIndex) => {\n                const nodes = namespaceGroups[ns];\n                const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n                const nsCenterX = centerX + nsRadius * Math.cos(nsAngle);\n                const nsCenterY = centerY + nsRadius * Math.sin(nsAngle);\n\n                const nodeCount = nodes.length;\n                const nodeRadius = Math.min(80, 20 + nodeCount * 5);\n\n                nodes.forEach((node, nodeIndex) => {\n                    if (nodeCount === 1) {\n                        node.x = nsCenterX;\n                        node.y = nsCenterY;\n                    } else {\n                        const nodeAngle = (2 * Math.PI * nodeIndex) \/ nodeCount;\n                        node.x = nsCenterX + nodeRadius * Math.cos(nodeAngle);\n                        node.y = nsCenterY + nodeRadius * Math.sin(nodeAngle);\n                    }\n                });\n            });\n\n            \/\/ Create node index for links\n            const nodeIndex = {};\n            data.nodes.forEach((n, i) => nodeIndex[n.id] = i);\n\n            \/\/ Resolve link indices\n            data.links.forEach(link => {\n                if (typeof link.source === 'number') {\n                    link.sourceNode = data.nodes[link.source];\n                    link.targetNode = data.nodes[link.target];\n                }\n            });\n\n            \/\/ Create SVG with zoom\n            const svg = d3.select('#graph-container')\n                .append('svg')\n                .attr('width', width)\n                .attr('height', height);\n\n            const g = svg.append('g');\n\n            const zoom = d3.zoom()\n                .scaleExtent([0.1, 4])\n                .on('zoom', (event) => {\n                    g.attr('transform', event.transform);\n                });\n\n            svg.call(zoom);\n\n            \/\/ Reset zoom button\n            document.getElementById('reset-zoom').addEventListener('click', () => {\n                svg.transition().duration(300).call(zoom.transform, d3.zoomIdentity);\n            });\n\n            \/\/ Arrow markers\n            svg.append('defs').selectAll('marker')\n                .data(['extends', 'implements', 'constructor', 'use', 'trait'])\n                .enter().append('marker')\n                .attr('id', d => 'arrow-global-' + d)\n                .attr('viewBox', '0 -5 10 10')\n                .attr('refX', 15)\n                .attr('refY', 0)\n                .attr('markerWidth', 5)\n                .attr('markerHeight', 5)\n                .attr('orient', 'auto')\n                .append('path')\n                .attr('d', 'M0,-5L10,0L0,5')\n                .attr('fill', d => linkColors[d]);\n\n            \/\/ Draw namespace labels\n            const nsLabels = g.append('g').attr('class', 'ns-labels');\n            nsKeys.forEach((ns, nsIndex) => {\n                const nsAngle = (2 * Math.PI * nsIndex) \/ nsCount - Math.PI \/ 2;\n                const labelRadius = nsRadius + 100;\n                const labelX = centerX + labelRadius * Math.cos(nsAngle);\n                const labelY = centerY + labelRadius * Math.sin(nsAngle);\n\n                nsLabels.append('text')\n                    .attr('x', labelX)\n                    .attr('y', labelY)\n                    .attr('text-anchor', 'middle')\n                    .attr('fill', 'var(--text-muted)')\n                    .attr('font-size', '10px')\n                    .attr('font-weight', 'bold')\n                    .text(ns.split('\\\\').pop());\n            });\n\n            \/\/ Draw links\n            const link = g.append('g')\n                .selectAll('line')\n                .data(data.links)\n                .enter().append('line')\n                .attr('class', 'graph-link')\n                .attr('data-type', d => d.type)\n                .attr('stroke', d => linkColors[d.type] || '#94a3b8')\n                .attr('stroke-width', 1.5)\n                .attr('stroke-opacity', 0.4)\n                .attr('marker-end', d => 'url(#arrow-global-' + d.type + ')')\n                .attr('x1', d => data.nodes[d.source].x)\n                .attr('y1', d => data.nodes[d.source].y)\n                .attr('x2', d => data.nodes[d.target].x)\n                .attr('y2', d => data.nodes[d.target].y);\n\n            \/\/ Dr... [TRUNCATED-302782fd32921f43]"
    }
}
← Vorheriger Zur Liste Nächster →