x-ipe 1.0.24__py3-none-any.whl → 1.0.25__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. x_ipe/app.py +25 -3
  2. x_ipe/handlers/terminal_handlers.py +6 -0
  3. x_ipe/handlers/voice_handlers.py +5 -0
  4. x_ipe/resources/copilot-instructions.md +19 -6
  5. x_ipe/resources/skills/lesson-learned/SKILL.md +208 -0
  6. x_ipe/resources/skills/lesson-learned/references/examples.md +238 -0
  7. x_ipe/resources/skills/project-quality-board-management/SKILL.md +135 -298
  8. x_ipe/resources/skills/project-quality-board-management/references/evaluation-principles.md +213 -0
  9. x_ipe/resources/skills/project-quality-board-management/references/evaluation-procedures.md +214 -0
  10. x_ipe/resources/skills/project-quality-board-management/templates/quality-report.md +70 -18
  11. x_ipe/resources/skills/task-execution-guideline/SKILL.md +2 -2
  12. x_ipe/resources/skills/task-execution-guideline/templates/task-record.yaml +1 -1
  13. x_ipe/resources/skills/task-type-code-implementation/SKILL.md +72 -270
  14. x_ipe/resources/skills/task-type-code-implementation/references/implementation-guidelines.md +432 -0
  15. x_ipe/resources/skills/task-type-code-refactor-v2/SKILL.md +127 -353
  16. x_ipe/resources/skills/task-type-code-refactor-v2/references/refactoring-techniques.md +373 -0
  17. x_ipe/resources/skills/task-type-feature-breakdown/SKILL.md +31 -243
  18. x_ipe/resources/skills/task-type-feature-breakdown/references/breakdown-guidelines.md +330 -0
  19. x_ipe/resources/skills/task-type-feature-refinement/SKILL.md +27 -180
  20. x_ipe/resources/skills/task-type-feature-refinement/references/specification-writing-guide.md +267 -0
  21. x_ipe/resources/skills/task-type-idea-mockup/SKILL.md +38 -276
  22. x_ipe/resources/skills/task-type-idea-mockup/references/mockup-guidelines.md +299 -0
  23. x_ipe/resources/skills/task-type-idea-to-architecture/SKILL.md +20 -218
  24. x_ipe/resources/skills/task-type-idea-to-architecture/references/architecture-patterns.md +342 -0
  25. x_ipe/resources/skills/task-type-ideation/SKILL.md +10 -266
  26. x_ipe/resources/skills/task-type-ideation/references/folder-naming-guide.md +55 -0
  27. x_ipe/resources/skills/task-type-ideation/references/tool-usage-guide.md +236 -0
  28. x_ipe/resources/skills/task-type-ideation-v2/SKILL.md +488 -0
  29. x_ipe/resources/skills/task-type-ideation-v2/references/examples.md +377 -0
  30. x_ipe/resources/skills/task-type-ideation-v2/references/folder-naming-guide.md +74 -0
  31. x_ipe/resources/skills/task-type-ideation-v2/references/tool-usage-guide.md +145 -0
  32. x_ipe/resources/skills/task-type-ideation-v2/references/visualization-guide.md +160 -0
  33. x_ipe/resources/skills/task-type-ideation-v2/templates/idea-summary.md +86 -0
  34. x_ipe/resources/skills/task-type-refactoring-analysis/SKILL.md +83 -145
  35. x_ipe/resources/skills/task-type-refactoring-analysis/references/output-schema.md +172 -0
  36. x_ipe/resources/skills/task-type-technical-design/SKILL.md +28 -214
  37. x_ipe/resources/skills/task-type-technical-design/references/design-templates.md +422 -0
  38. x_ipe/resources/skills/task-type-test-generation/SKILL.md +47 -332
  39. x_ipe/resources/skills/task-type-test-generation/references/test-patterns.md +368 -0
  40. x_ipe/resources/skills/tool-tracing-creator/SKILL.md +312 -0
  41. x_ipe/resources/skills/tool-tracing-creator/references/examples.md +324 -0
  42. x_ipe/resources/skills/tool-tracing-instrumentation/SKILL.md +373 -0
  43. x_ipe/resources/skills/tool-tracing-instrumentation/references/examples.md +264 -0
  44. x_ipe/resources/skills/x-ipe-skill-creator-v3/SKILL.md +486 -0
  45. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/10. example-gate-conditions.md +73 -0
  46. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/11. reference-quality-standards.md +127 -0
  47. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/2. reference-section-order.md +127 -0
  48. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/3. example-step-based-code-review.md +84 -0
  49. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/4. example-step-based-feature-implementation.md +113 -0
  50. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/5. example-function-based-validation.md +73 -0
  51. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/6. example-function-based-analysis.md +94 -0
  52. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/7. example-task-io-code-implementation.md +36 -0
  53. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/8. example-structured-summary.md +43 -0
  54. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/9. example-dor-dod.md +77 -0
  55. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/examples.md +429 -0
  56. x_ipe/resources/skills/x-ipe-skill-creator-v3/references/skill-general-guidelines-v2.md +611 -0
  57. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-meta.md +153 -0
  58. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-based.md +324 -0
  59. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-task-category.md +109 -0
  60. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/skill-meta-x-ipe-tool.md +205 -0
  61. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-meta.md +334 -0
  62. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-task-based.md +279 -0
  63. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-tool.md +175 -0
  64. x_ipe/resources/skills/x-ipe-skill-creator-v3/templates/x-ipe-workflow-orchestration.md +329 -0
  65. x_ipe/resources/skills/x-ipe-task-based-ideation/SKILL.md +487 -0
  66. x_ipe/resources/skills/x-ipe-task-based-ideation/references/examples.md +377 -0
  67. x_ipe/resources/skills/x-ipe-task-based-ideation/references/folder-naming-guide.md +74 -0
  68. x_ipe/resources/skills/x-ipe-task-based-ideation/references/tool-usage-guide.md +145 -0
  69. x_ipe/resources/skills/x-ipe-task-based-ideation/references/visualization-guide.md +160 -0
  70. x_ipe/resources/skills/x-ipe-task-based-ideation/templates/idea-summary.md +86 -0
  71. x_ipe/routes/__init__.py +2 -0
  72. x_ipe/routes/ideas_routes.py +17 -0
  73. x_ipe/routes/kb_routes.py +80 -0
  74. x_ipe/routes/main_routes.py +18 -0
  75. x_ipe/routes/project_routes.py +7 -0
  76. x_ipe/routes/proxy_routes.py +2 -0
  77. x_ipe/routes/quality_evaluation_routes.py +193 -0
  78. x_ipe/routes/settings_routes.py +6 -0
  79. x_ipe/routes/tools_routes.py +6 -0
  80. x_ipe/routes/tracing_routes.py +232 -0
  81. x_ipe/routes/uiux_feedback_routes.py +30 -0
  82. x_ipe/services/__init__.py +5 -0
  83. x_ipe/services/config_service.py +6 -0
  84. x_ipe/services/file_service.py +20 -0
  85. x_ipe/services/homepage_service.py +160 -0
  86. x_ipe/services/ideas_service.py +19 -0
  87. x_ipe/services/kb_service.py +378 -0
  88. x_ipe/services/proxy_service.py +4 -0
  89. x_ipe/services/settings_service.py +13 -0
  90. x_ipe/services/skills_service.py +4 -0
  91. x_ipe/services/terminal_service.py +24 -0
  92. x_ipe/services/themes_service.py +4 -0
  93. x_ipe/services/tools_config_service.py +4 -0
  94. x_ipe/services/tracing_service.py +333 -0
  95. x_ipe/services/uiux_feedback_service.py +32 -0
  96. x_ipe/services/voice_input_service_v2.py +11 -0
  97. x_ipe/static/css/base.css +7 -0
  98. x_ipe/static/css/homepage-infinity.css +330 -0
  99. x_ipe/static/css/kb-core.css +301 -0
  100. x_ipe/static/css/quality-evaluation.css +345 -0
  101. x_ipe/static/css/sidebar.css +14 -4
  102. x_ipe/static/css/terminal.css +1 -0
  103. x_ipe/static/css/tracing-dashboard.css +796 -0
  104. x_ipe/static/css/workplace.css +20 -0
  105. x_ipe/static/img/homepage-infinity-loop.png +0 -0
  106. x_ipe/static/js/features/homepage-infinity.js +314 -0
  107. x_ipe/static/js/features/kb-core.js +371 -0
  108. x_ipe/static/js/features/quality-evaluation.js +387 -0
  109. x_ipe/static/js/features/sidebar.js +255 -12
  110. x_ipe/static/js/features/tracing-dashboard.js +855 -0
  111. x_ipe/static/js/features/tracing-graph.js +1031 -0
  112. x_ipe/static/js/features/tree-search.js +6 -2
  113. x_ipe/static/js/features/workplace.js +200 -6
  114. x_ipe/static/js/init.js +76 -0
  115. x_ipe/static/js/uiux-feedback.js +18 -2
  116. x_ipe/templates/base.html +19 -0
  117. x_ipe/templates/index.html +7 -1
  118. x_ipe/templates/knowledge-base.html +110 -0
  119. x_ipe/templates/workplace.html +4 -0
  120. x_ipe/tracing/__init__.py +37 -0
  121. x_ipe/tracing/buffer.py +135 -0
  122. x_ipe/tracing/context.py +125 -0
  123. x_ipe/tracing/decorator.py +288 -0
  124. x_ipe/tracing/middleware.py +197 -0
  125. x_ipe/tracing/parser.py +235 -0
  126. x_ipe/tracing/redactor.py +111 -0
  127. x_ipe/tracing/writer.py +122 -0
  128. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/METADATA +2 -2
  129. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/RECORD +132 -62
  130. x_ipe/resources/skills/x-ipe-skill-creator/SKILL.md +0 -329
  131. x_ipe/resources/skills/x-ipe-skill-creator/references/output-patterns.md +0 -169
  132. x_ipe/resources/skills/x-ipe-skill-creator/references/skill-structure.md +0 -162
  133. x_ipe/resources/skills/x-ipe-skill-creator/references/workflows.md +0 -110
  134. x_ipe/resources/skills/x-ipe-skill-creator/templates/references/examples.md +0 -113
  135. x_ipe/resources/skills/x-ipe-skill-creator/templates/skill-category-skill.md +0 -296
  136. x_ipe/resources/skills/x-ipe-skill-creator/templates/task-type-skill.md +0 -269
  137. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/WHEEL +0 -0
  138. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/entry_points.txt +0 -0
  139. {x_ipe-1.0.24.dist-info → x_ipe-1.0.25.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1031 @@
1
+ /**
2
+ * FEATURE-023-C: Trace Viewer & DAG Visualization
3
+ *
4
+ * Graph visualization component for trace data using G6 by AntV.
5
+ * Creates interactive DAG showing function call hierarchy with timing.
6
+ */
7
+
8
+ // =============================================================================
9
+ // TracingGraphView - DAG Visualization Component
10
+ // =============================================================================
11
+
12
+ // Static flag to track if custom G6 nodes/edges have been registered
13
+ let _g6CustomTypesRegistered = false;
14
+
15
+ class TracingGraphView {
16
+ /**
17
+ * Create a new TracingGraphView
18
+ * @param {HTMLElement} container - Container element for the graph
19
+ * @param {Object} options - Configuration options
20
+ */
21
+ constructor(container, options = {}) {
22
+ this.container = container;
23
+ this.graph = null;
24
+ this.data = null;
25
+ this.selectedNode = null;
26
+ this.modal = null;
27
+
28
+ // Default options
29
+ this.options = {
30
+ onNodeClick: options.onNodeClick || null,
31
+ minZoom: options.minZoom || 0.2,
32
+ maxZoom: options.maxZoom || 3,
33
+ fitCenter: options.fitCenter !== false,
34
+ animate: options.animate !== false,
35
+ ...options
36
+ };
37
+ }
38
+
39
+ // -------------------------------------------------------------------------
40
+ // Graph Lifecycle
41
+ // -------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Initialize the graph visualization
45
+ */
46
+ init() {
47
+ if (!this.container) {
48
+ console.error('[TracingGraphView] Container not found');
49
+ return;
50
+ }
51
+
52
+ // Clear existing graph
53
+ if (this.graph) {
54
+ this.graph.destroy();
55
+ this.graph = null;
56
+ }
57
+
58
+ const { width, height } = this.getContainerSize();
59
+
60
+ // Initialize G6 graph with dagre layout
61
+ this.graph = new G6.Graph({
62
+ container: this.container,
63
+ width,
64
+ height,
65
+ fitView: this.options.fitCenter,
66
+ fitViewPadding: 40,
67
+ minZoom: this.options.minZoom,
68
+ maxZoom: this.options.maxZoom,
69
+ modes: {
70
+ default: ['drag-canvas', 'zoom-canvas', 'drag-node']
71
+ },
72
+ layout: {
73
+ type: 'dagre',
74
+ rankdir: 'LR', // Left to right
75
+ align: 'UL',
76
+ nodesep: 30, // Node separation
77
+ ranksep: 80, // Rank separation
78
+ controlPoints: true
79
+ },
80
+ defaultNode: {
81
+ type: 'trace-node',
82
+ size: [180, 60]
83
+ },
84
+ defaultEdge: {
85
+ type: 'trace-edge',
86
+ style: {
87
+ lineWidth: 2,
88
+ endArrow: {
89
+ path: G6.Arrow.triangle(6, 8, 0),
90
+ fill: '#aab7c4'
91
+ }
92
+ }
93
+ }
94
+ });
95
+
96
+ // Register custom node and edge (only once globally)
97
+ if (!_g6CustomTypesRegistered) {
98
+ this.registerCustomNode();
99
+ this.registerCustomEdge();
100
+ _g6CustomTypesRegistered = true;
101
+ }
102
+
103
+ // Bind events
104
+ this.bindEvents();
105
+ }
106
+
107
+ /**
108
+ * Destroy the graph and cleanup
109
+ */
110
+ destroy() {
111
+ if (this.modal) {
112
+ this.modal.close();
113
+ this.modal = null;
114
+ }
115
+ if (this.graph) {
116
+ this.graph.destroy();
117
+ this.graph = null;
118
+ }
119
+ }
120
+
121
+ // -------------------------------------------------------------------------
122
+ // Custom Node Registration
123
+ // -------------------------------------------------------------------------
124
+
125
+ registerCustomNode() {
126
+ G6.registerNode('trace-node', {
127
+ draw(cfg, group) {
128
+ const { label, timing, status, level, error } = cfg;
129
+ const width = 180;
130
+ const height = 60;
131
+
132
+ // Determine colors based on status and level
133
+ const isError = status === 'error';
134
+ const isApi = level === 'API';
135
+
136
+ let bgColor = '#ffffff';
137
+ let borderColor = '#d0d7de';
138
+ let labelColor = '#1f2328';
139
+ let timingColor = '#656d76';
140
+
141
+ if (isError) {
142
+ bgColor = '#ffebe9';
143
+ borderColor = '#ff8182';
144
+ labelColor = '#cf222e';
145
+ }
146
+ if (isApi) {
147
+ bgColor = isError ? '#ffebe9' : '#ddf4ff';
148
+ borderColor = isError ? '#ff8182' : '#54aeff';
149
+ labelColor = isError ? '#cf222e' : '#0969da';
150
+ }
151
+
152
+ // Main rectangle
153
+ const shape = group.addShape('rect', {
154
+ attrs: {
155
+ x: -width / 2,
156
+ y: -height / 2,
157
+ width,
158
+ height,
159
+ radius: 8,
160
+ fill: bgColor,
161
+ stroke: borderColor,
162
+ lineWidth: 2,
163
+ cursor: 'pointer',
164
+ shadowColor: 'rgba(0,0,0,0.1)',
165
+ shadowBlur: 4,
166
+ shadowOffsetY: 2
167
+ },
168
+ name: 'main-box'
169
+ });
170
+
171
+ // Function name
172
+ const displayLabel = label.length > 20 ? label.substring(0, 17) + '...' : label;
173
+ group.addShape('text', {
174
+ attrs: {
175
+ x: 0,
176
+ y: -8,
177
+ text: displayLabel,
178
+ fontSize: 13,
179
+ fontWeight: 600,
180
+ fill: labelColor,
181
+ textAlign: 'center',
182
+ textBaseline: 'middle',
183
+ cursor: 'pointer'
184
+ },
185
+ name: 'label'
186
+ });
187
+
188
+ // Timing badge
189
+ if (timing) {
190
+ group.addShape('text', {
191
+ attrs: {
192
+ x: 0,
193
+ y: 14,
194
+ text: timing,
195
+ fontSize: 11,
196
+ fill: timingColor,
197
+ textAlign: 'center',
198
+ textBaseline: 'middle'
199
+ },
200
+ name: 'timing'
201
+ });
202
+ }
203
+
204
+ // Error indicator
205
+ if (isError && error) {
206
+ group.addShape('text', {
207
+ attrs: {
208
+ x: width / 2 - 16,
209
+ y: -height / 2 + 12,
210
+ text: '⚠',
211
+ fontSize: 12,
212
+ fill: '#cf222e',
213
+ textAlign: 'center'
214
+ },
215
+ name: 'error-icon'
216
+ });
217
+ }
218
+
219
+ // Level badge (for non-API nodes)
220
+ if (!isApi) {
221
+ const badgeColor = level === 'DEBUG' ? '#8250df' : '#2da44e';
222
+ group.addShape('rect', {
223
+ attrs: {
224
+ x: -width / 2 + 6,
225
+ y: -height / 2 + 6,
226
+ width: level === 'DEBUG' ? 42 : 32,
227
+ height: 16,
228
+ radius: 3,
229
+ fill: badgeColor
230
+ },
231
+ name: 'level-badge-bg'
232
+ });
233
+ group.addShape('text', {
234
+ attrs: {
235
+ x: -width / 2 + (level === 'DEBUG' ? 27 : 22),
236
+ y: -height / 2 + 14,
237
+ text: level,
238
+ fontSize: 9,
239
+ fontWeight: 500,
240
+ fill: '#ffffff',
241
+ textAlign: 'center',
242
+ textBaseline: 'middle'
243
+ },
244
+ name: 'level-badge'
245
+ });
246
+ }
247
+
248
+ return shape;
249
+ },
250
+
251
+ setState(name, value, item) {
252
+ const group = item.getContainer();
253
+ const mainBox = group.find(e => e.get('name') === 'main-box');
254
+
255
+ if (name === 'hover') {
256
+ if (value) {
257
+ mainBox.attr('shadowBlur', 8);
258
+ mainBox.attr('shadowOffsetY', 4);
259
+ } else {
260
+ mainBox.attr('shadowBlur', 4);
261
+ mainBox.attr('shadowOffsetY', 2);
262
+ }
263
+ }
264
+
265
+ if (name === 'selected') {
266
+ const cfg = item.getModel();
267
+ const isError = cfg.status === 'error';
268
+ const isApi = cfg.level === 'API';
269
+
270
+ if (value) {
271
+ mainBox.attr('lineWidth', 3);
272
+ mainBox.attr('stroke', isError ? '#cf222e' : (isApi ? '#0969da' : '#1f883d'));
273
+ } else {
274
+ mainBox.attr('lineWidth', 2);
275
+ mainBox.attr('stroke', isError ? '#ff8182' : (isApi ? '#54aeff' : '#d0d7de'));
276
+ }
277
+ }
278
+ }
279
+ }, 'single-node');
280
+ }
281
+
282
+ // -------------------------------------------------------------------------
283
+ // Custom Edge Registration
284
+ // -------------------------------------------------------------------------
285
+
286
+ registerCustomEdge() {
287
+ G6.registerEdge('trace-edge', {
288
+ draw(cfg, group) {
289
+ const startPoint = cfg.startPoint;
290
+ const endPoint = cfg.endPoint;
291
+ const controlPoints = cfg.controlPoints || [];
292
+
293
+ // Build path through control points
294
+ let path = [['M', startPoint.x, startPoint.y]];
295
+
296
+ if (controlPoints.length > 0) {
297
+ controlPoints.forEach(cp => {
298
+ path.push(['L', cp.x, cp.y]);
299
+ });
300
+ }
301
+
302
+ path.push(['L', endPoint.x, endPoint.y]);
303
+
304
+ // Get source node status for edge color
305
+ const sourceNode = cfg.sourceNode;
306
+ const model = sourceNode ? sourceNode.getModel() : {};
307
+ const isError = model.status === 'error';
308
+
309
+ const shape = group.addShape('path', {
310
+ attrs: {
311
+ path,
312
+ stroke: isError ? '#ff8182' : '#aab7c4',
313
+ lineWidth: 2,
314
+ endArrow: {
315
+ path: G6.Arrow.triangle(6, 8, 0),
316
+ fill: isError ? '#ff8182' : '#aab7c4'
317
+ }
318
+ },
319
+ name: 'edge-path'
320
+ });
321
+
322
+ return shape;
323
+ },
324
+
325
+ setState(name, value, item) {
326
+ const group = item.getContainer();
327
+ const edgePath = group.find(e => e.get('name') === 'edge-path');
328
+
329
+ if (name === 'highlight') {
330
+ if (value) {
331
+ edgePath.attr('lineWidth', 3);
332
+ edgePath.attr('stroke', '#0969da');
333
+ } else {
334
+ const model = item.getSource().getModel();
335
+ const isError = model.status === 'error';
336
+ edgePath.attr('lineWidth', 2);
337
+ edgePath.attr('stroke', isError ? '#ff8182' : '#aab7c4');
338
+ }
339
+ }
340
+ }
341
+ }, 'polyline');
342
+ }
343
+
344
+ // -------------------------------------------------------------------------
345
+ // Event Binding
346
+ // -------------------------------------------------------------------------
347
+
348
+ bindEvents() {
349
+ if (!this.graph) return;
350
+
351
+ // Node hover
352
+ this.graph.on('node:mouseenter', (evt) => {
353
+ this.graph.setItemState(evt.item, 'hover', true);
354
+ this.container.style.cursor = 'pointer';
355
+ });
356
+
357
+ this.graph.on('node:mouseleave', (evt) => {
358
+ this.graph.setItemState(evt.item, 'hover', false);
359
+ this.container.style.cursor = 'default';
360
+ });
361
+
362
+ // Node click
363
+ this.graph.on('node:click', (evt) => {
364
+ const node = evt.item;
365
+ const model = node.getModel();
366
+
367
+ // Deselect previous
368
+ if (this.selectedNode) {
369
+ this.graph.setItemState(this.selectedNode, 'selected', false);
370
+ }
371
+
372
+ // Select new
373
+ this.graph.setItemState(node, 'selected', true);
374
+ this.selectedNode = node;
375
+
376
+ // Show modal or callback
377
+ if (this.options.onNodeClick) {
378
+ this.options.onNodeClick(model);
379
+ } else {
380
+ this.showNodeModal(model);
381
+ }
382
+ });
383
+
384
+ // Canvas click (deselect)
385
+ this.graph.on('canvas:click', () => {
386
+ if (this.selectedNode) {
387
+ this.graph.setItemState(this.selectedNode, 'selected', false);
388
+ this.selectedNode = null;
389
+ }
390
+ });
391
+
392
+ // Window resize
393
+ window.addEventListener('resize', this.handleResize.bind(this));
394
+ }
395
+
396
+ // -------------------------------------------------------------------------
397
+ // Data Loading
398
+ // -------------------------------------------------------------------------
399
+
400
+ /**
401
+ * Load trace data from API
402
+ * @param {string} traceId - Trace ID to load
403
+ */
404
+ async loadTrace(traceId) {
405
+ this.showLoading();
406
+
407
+ try {
408
+ const response = await fetch(`/api/tracing/logs/${encodeURIComponent(traceId)}`);
409
+
410
+ if (!response.ok) {
411
+ if (response.status === 404) {
412
+ this.showError('Trace not found');
413
+ } else {
414
+ throw new Error(`API error: ${response.status}`);
415
+ }
416
+ return;
417
+ }
418
+
419
+ const data = await response.json();
420
+ this.data = data;
421
+ this.renderGraph(data);
422
+
423
+ } catch (error) {
424
+ console.error('[TracingGraphView] Load error:', error);
425
+ this.showError('Failed to load trace data');
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Load trace data directly (without API call)
431
+ * @param {Object} data - Pre-loaded trace data
432
+ */
433
+ setData(data) {
434
+ this.data = data;
435
+ if (!this.graph) {
436
+ this.init();
437
+ }
438
+ this.renderGraph(data);
439
+ }
440
+
441
+ // -------------------------------------------------------------------------
442
+ // Graph Rendering
443
+ // -------------------------------------------------------------------------
444
+
445
+ renderGraph(data) {
446
+ if (!data) return;
447
+
448
+ // Clear any loading/empty state and re-initialize graph
449
+ // (showLoading/showEmpty may have replaced the container's innerHTML)
450
+ this.container.innerHTML = '';
451
+
452
+ // Reset selected node reference (old node belongs to destroyed graph)
453
+ this.selectedNode = null;
454
+
455
+ // Close any existing modal
456
+ if (this.modal) {
457
+ this.modal.close();
458
+ this.modal = null;
459
+ }
460
+
461
+ // Destroy existing graph if any (may fail if DOM was already cleared)
462
+ if (this.graph) {
463
+ try {
464
+ this.graph.destroy();
465
+ } catch (e) {
466
+ // Graph's DOM element may have been cleared by showLoading
467
+ }
468
+ this.graph = null;
469
+ }
470
+
471
+ // Initialize fresh graph
472
+ this.init();
473
+
474
+ if (!this.graph) return;
475
+
476
+ // Transform to G6 format
477
+ const graphData = {
478
+ nodes: (data.nodes || []).map(node => ({
479
+ id: node.id,
480
+ label: node.label,
481
+ timing: node.timing,
482
+ status: node.status,
483
+ level: node.level,
484
+ input: node.input,
485
+ output: node.output,
486
+ error: node.error
487
+ })),
488
+ edges: (data.edges || []).map(edge => ({
489
+ source: edge.source,
490
+ target: edge.target
491
+ }))
492
+ };
493
+
494
+ // Render graph
495
+ this.graph.data(graphData);
496
+ this.graph.render();
497
+
498
+ // Fit view with animation
499
+ if (this.options.animate) {
500
+ setTimeout(() => {
501
+ this.graph.fitView(40);
502
+ }, 100);
503
+ } else {
504
+ this.graph.fitView(40);
505
+ }
506
+ }
507
+
508
+ // -------------------------------------------------------------------------
509
+ // UI States
510
+ // -------------------------------------------------------------------------
511
+
512
+ showLoading() {
513
+ this.container.innerHTML = `
514
+ <div class="trace-graph-loading">
515
+ <div class="spinner-border spinner-border-sm" role="status"></div>
516
+ <span>Loading trace...</span>
517
+ </div>
518
+ `;
519
+ }
520
+
521
+ showError(message) {
522
+ this.container.innerHTML = `
523
+ <div class="trace-graph-error">
524
+ <i class="bi bi-exclamation-triangle"></i>
525
+ <span>${message}</span>
526
+ </div>
527
+ `;
528
+ }
529
+
530
+ showEmpty() {
531
+ this.container.innerHTML = `
532
+ <div class="trace-graph-empty">
533
+ <i class="bi bi-diagram-3"></i>
534
+ <span>Select a trace to visualize</span>
535
+ </div>
536
+ `;
537
+ }
538
+
539
+ // -------------------------------------------------------------------------
540
+ // Node Modal
541
+ // -------------------------------------------------------------------------
542
+
543
+ showNodeModal(node) {
544
+ if (this.modal) {
545
+ this.modal.close();
546
+ }
547
+ this.modal = new TracingNodeModal(node);
548
+ this.modal.open();
549
+ }
550
+
551
+ // -------------------------------------------------------------------------
552
+ // Zoom Controls
553
+ // -------------------------------------------------------------------------
554
+
555
+ zoomIn() {
556
+ if (!this.graph) return;
557
+ const currentZoom = this.graph.getZoom();
558
+ const newZoom = Math.min(currentZoom * 1.2, this.options.maxZoom);
559
+ this.graph.zoomTo(newZoom, undefined, true);
560
+ }
561
+
562
+ zoomOut() {
563
+ if (!this.graph) return;
564
+ const currentZoom = this.graph.getZoom();
565
+ const newZoom = Math.max(currentZoom / 1.2, this.options.minZoom);
566
+ this.graph.zoomTo(newZoom, undefined, true);
567
+ }
568
+
569
+ resetZoom() {
570
+ if (!this.graph) return;
571
+ this.graph.fitView(40, undefined, true);
572
+ }
573
+
574
+ // -------------------------------------------------------------------------
575
+ // Utilities
576
+ // -------------------------------------------------------------------------
577
+
578
+ getContainerSize() {
579
+ const rect = this.container.getBoundingClientRect();
580
+ return {
581
+ width: rect.width || 800,
582
+ height: rect.height || 400
583
+ };
584
+ }
585
+
586
+ handleResize() {
587
+ if (!this.graph) return;
588
+ const { width, height } = this.getContainerSize();
589
+ this.graph.changeSize(width, height);
590
+ }
591
+ }
592
+
593
+
594
+ // =============================================================================
595
+ // TracingNodeModal - Node Detail Modal
596
+ // =============================================================================
597
+
598
+ class TracingNodeModal {
599
+ constructor(node) {
600
+ this.node = node;
601
+ this.overlay = null;
602
+ }
603
+
604
+ open() {
605
+ this.createOverlay();
606
+ document.body.appendChild(this.overlay);
607
+
608
+ // Animate in
609
+ requestAnimationFrame(() => {
610
+ this.overlay.classList.add('show');
611
+ });
612
+ }
613
+
614
+ close() {
615
+ if (!this.overlay) return;
616
+
617
+ this.overlay.classList.remove('show');
618
+ setTimeout(() => {
619
+ this.overlay?.remove();
620
+ this.overlay = null;
621
+ }, 200);
622
+ }
623
+
624
+ createOverlay() {
625
+ const { label, timing, status, level, input, output, error } = this.node;
626
+ const isError = status === 'error';
627
+
628
+ this.overlay = document.createElement('div');
629
+ this.overlay.className = 'trace-node-modal-overlay';
630
+ this.overlay.innerHTML = `
631
+ <div class="trace-node-modal">
632
+ <div class="trace-node-modal-header ${isError ? 'error' : ''}">
633
+ <div class="trace-node-modal-title">
634
+ <span class="trace-node-level-badge ${level.toLowerCase()}">${level}</span>
635
+ <span class="trace-node-name">${this.escapeHtml(label)}</span>
636
+ </div>
637
+ <button class="trace-node-modal-close" aria-label="Close">×</button>
638
+ </div>
639
+
640
+ <div class="trace-node-modal-body">
641
+ <div class="trace-node-info-row">
642
+ <span class="trace-node-info-label">Status:</span>
643
+ <span class="trace-node-status ${status}">${status}</span>
644
+ </div>
645
+
646
+ ${timing ? `
647
+ <div class="trace-node-info-row">
648
+ <span class="trace-node-info-label">Duration:</span>
649
+ <span class="trace-node-timing">${timing}</span>
650
+ </div>
651
+ ` : ''}
652
+
653
+ ${error ? `
654
+ <div class="trace-node-section error">
655
+ <div class="trace-node-section-title">
656
+ <i class="bi bi-exclamation-triangle"></i> Error
657
+ </div>
658
+ <div class="trace-node-error-type">${this.escapeHtml(error.type)}</div>
659
+ <div class="trace-node-error-message">${this.escapeHtml(error.message)}</div>
660
+ ${error.stack && error.stack.length > 0 ? `
661
+ <div class="trace-node-stack">
662
+ <div class="trace-node-section-title">Stack Trace</div>
663
+ ${error.stack.map(s => `
664
+ <div class="trace-node-stack-line">
665
+ at <strong>${this.escapeHtml(s.func)}</strong>
666
+ (${this.escapeHtml(s.file)}${s.line ? ':' + s.line : ''})
667
+ </div>
668
+ `).join('')}
669
+ </div>
670
+ ` : ''}
671
+ </div>
672
+ ` : ''}
673
+
674
+ <div class="trace-node-section">
675
+ <div class="trace-node-section-title">
676
+ <i class="bi bi-box-arrow-in-right"></i> Input
677
+ </div>
678
+ <pre class="trace-node-json">${this.formatJson(input)}</pre>
679
+ </div>
680
+
681
+ ${!error ? `
682
+ <div class="trace-node-section">
683
+ <div class="trace-node-section-title">
684
+ <i class="bi bi-box-arrow-right"></i> Output
685
+ </div>
686
+ <pre class="trace-node-json">${this.formatJson(output)}</pre>
687
+ </div>
688
+ ` : ''}
689
+ </div>
690
+ </div>
691
+ `;
692
+
693
+ // Bind events
694
+ this.overlay.addEventListener('click', (e) => {
695
+ if (e.target === this.overlay) {
696
+ this.close();
697
+ }
698
+ });
699
+
700
+ this.overlay.querySelector('.trace-node-modal-close').addEventListener('click', () => {
701
+ this.close();
702
+ });
703
+
704
+ // ESC key to close
705
+ document.addEventListener('keydown', this.handleKeyDown = (e) => {
706
+ if (e.key === 'Escape') {
707
+ this.close();
708
+ document.removeEventListener('keydown', this.handleKeyDown);
709
+ }
710
+ });
711
+ }
712
+
713
+ formatJson(jsonStr) {
714
+ if (!jsonStr || jsonStr === '{}') {
715
+ return '<span class="trace-json-empty">(empty)</span>';
716
+ }
717
+ try {
718
+ const obj = typeof jsonStr === 'string' ? JSON.parse(jsonStr) : jsonStr;
719
+ return this.escapeHtml(JSON.stringify(obj, null, 2));
720
+ } catch {
721
+ return this.escapeHtml(jsonStr);
722
+ }
723
+ }
724
+
725
+ escapeHtml(str) {
726
+ if (!str) return '';
727
+ const div = document.createElement('div');
728
+ div.textContent = str;
729
+ return div.innerHTML;
730
+ }
731
+ }
732
+
733
+
734
+ // =============================================================================
735
+ // CSS Styles (injected on first use)
736
+ // =============================================================================
737
+
738
+ (function injectStyles() {
739
+ if (document.getElementById('tracing-graph-styles')) return;
740
+
741
+ const style = document.createElement('style');
742
+ style.id = 'tracing-graph-styles';
743
+ style.textContent = `
744
+ /* Graph container states */
745
+ .trace-graph-loading,
746
+ .trace-graph-error,
747
+ .trace-graph-empty {
748
+ display: flex;
749
+ flex-direction: column;
750
+ align-items: center;
751
+ justify-content: center;
752
+ height: 100%;
753
+ min-height: 300px;
754
+ gap: 8px;
755
+ color: #656d76;
756
+ font-size: 14px;
757
+ }
758
+
759
+ .trace-graph-loading .spinner-border {
760
+ margin-right: 8px;
761
+ }
762
+
763
+ .trace-graph-error {
764
+ color: #cf222e;
765
+ }
766
+
767
+ .trace-graph-error i,
768
+ .trace-graph-empty i {
769
+ font-size: 32px;
770
+ opacity: 0.5;
771
+ }
772
+
773
+ /* Node modal overlay */
774
+ .trace-node-modal-overlay {
775
+ position: fixed;
776
+ top: 0;
777
+ left: 0;
778
+ right: 0;
779
+ bottom: 0;
780
+ background: rgba(0, 0, 0, 0.5);
781
+ display: flex;
782
+ align-items: center;
783
+ justify-content: center;
784
+ z-index: 10000;
785
+ opacity: 0;
786
+ transition: opacity 0.2s ease;
787
+ }
788
+
789
+ .trace-node-modal-overlay.show {
790
+ opacity: 1;
791
+ }
792
+
793
+ /* Node modal */
794
+ .trace-node-modal {
795
+ background: #ffffff;
796
+ border-radius: 12px;
797
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
798
+ width: 90%;
799
+ max-width: 560px;
800
+ max-height: 80vh;
801
+ overflow: hidden;
802
+ display: flex;
803
+ flex-direction: column;
804
+ transform: scale(0.95);
805
+ transition: transform 0.2s ease;
806
+ }
807
+
808
+ .trace-node-modal-overlay.show .trace-node-modal {
809
+ transform: scale(1);
810
+ }
811
+
812
+ /* Modal header */
813
+ .trace-node-modal-header {
814
+ display: flex;
815
+ align-items: center;
816
+ justify-content: space-between;
817
+ padding: 16px 20px;
818
+ border-bottom: 1px solid #d0d7de;
819
+ background: #f6f8fa;
820
+ }
821
+
822
+ .trace-node-modal-header.error {
823
+ background: #ffebe9;
824
+ border-color: #ff8182;
825
+ }
826
+
827
+ .trace-node-modal-title {
828
+ display: flex;
829
+ align-items: center;
830
+ gap: 10px;
831
+ font-weight: 600;
832
+ }
833
+
834
+ .trace-node-name {
835
+ font-size: 16px;
836
+ color: #1f2328;
837
+ }
838
+
839
+ .trace-node-level-badge {
840
+ padding: 2px 8px;
841
+ border-radius: 4px;
842
+ font-size: 11px;
843
+ font-weight: 600;
844
+ text-transform: uppercase;
845
+ }
846
+
847
+ .trace-node-level-badge.api {
848
+ background: #ddf4ff;
849
+ color: #0969da;
850
+ }
851
+
852
+ .trace-node-level-badge.info {
853
+ background: #dafbe1;
854
+ color: #1a7f37;
855
+ }
856
+
857
+ .trace-node-level-badge.debug {
858
+ background: #fbefff;
859
+ color: #8250df;
860
+ }
861
+
862
+ .trace-node-modal-close {
863
+ background: none;
864
+ border: none;
865
+ font-size: 24px;
866
+ color: #656d76;
867
+ cursor: pointer;
868
+ padding: 0;
869
+ width: 32px;
870
+ height: 32px;
871
+ border-radius: 6px;
872
+ display: flex;
873
+ align-items: center;
874
+ justify-content: center;
875
+ }
876
+
877
+ .trace-node-modal-close:hover {
878
+ background: rgba(0, 0, 0, 0.05);
879
+ color: #1f2328;
880
+ }
881
+
882
+ /* Modal body */
883
+ .trace-node-modal-body {
884
+ padding: 20px;
885
+ overflow-y: auto;
886
+ }
887
+
888
+ .trace-node-info-row {
889
+ display: flex;
890
+ gap: 12px;
891
+ margin-bottom: 12px;
892
+ }
893
+
894
+ .trace-node-info-label {
895
+ color: #656d76;
896
+ min-width: 70px;
897
+ }
898
+
899
+ .trace-node-status {
900
+ font-weight: 500;
901
+ }
902
+
903
+ .trace-node-status.success {
904
+ color: #1a7f37;
905
+ }
906
+
907
+ .trace-node-status.error {
908
+ color: #cf222e;
909
+ }
910
+
911
+ .trace-node-timing {
912
+ font-family: ui-monospace, monospace;
913
+ color: #0969da;
914
+ }
915
+
916
+ /* Sections */
917
+ .trace-node-section {
918
+ margin-top: 16px;
919
+ padding-top: 16px;
920
+ border-top: 1px solid #d0d7de;
921
+ }
922
+
923
+ .trace-node-section.error {
924
+ background: #ffebe9;
925
+ margin: 16px -20px;
926
+ padding: 16px 20px;
927
+ border-top: none;
928
+ }
929
+
930
+ .trace-node-section-title {
931
+ font-size: 12px;
932
+ font-weight: 600;
933
+ text-transform: uppercase;
934
+ color: #656d76;
935
+ margin-bottom: 8px;
936
+ display: flex;
937
+ align-items: center;
938
+ gap: 6px;
939
+ }
940
+
941
+ .trace-node-section.error .trace-node-section-title {
942
+ color: #cf222e;
943
+ }
944
+
945
+ .trace-node-error-type {
946
+ font-weight: 600;
947
+ color: #cf222e;
948
+ margin-bottom: 4px;
949
+ }
950
+
951
+ .trace-node-error-message {
952
+ color: #57606a;
953
+ }
954
+
955
+ .trace-node-stack {
956
+ margin-top: 12px;
957
+ }
958
+
959
+ .trace-node-stack-line {
960
+ font-family: ui-monospace, monospace;
961
+ font-size: 12px;
962
+ color: #57606a;
963
+ padding: 2px 0;
964
+ }
965
+
966
+ .trace-node-stack-line strong {
967
+ color: #1f2328;
968
+ }
969
+
970
+ /* JSON display */
971
+ .trace-node-json {
972
+ background: #f6f8fa;
973
+ border: 1px solid #d0d7de;
974
+ border-radius: 6px;
975
+ padding: 12px;
976
+ font-family: ui-monospace, monospace;
977
+ font-size: 12px;
978
+ overflow-x: auto;
979
+ margin: 0;
980
+ max-height: 150px;
981
+ overflow-y: auto;
982
+ }
983
+
984
+ .trace-json-empty {
985
+ color: #656d76;
986
+ font-style: italic;
987
+ }
988
+
989
+ /* Zoom controls */
990
+ .trace-graph-zoom-controls {
991
+ position: absolute;
992
+ bottom: 16px;
993
+ right: 16px;
994
+ display: flex;
995
+ gap: 4px;
996
+ background: #ffffff;
997
+ border: 1px solid #d0d7de;
998
+ border-radius: 6px;
999
+ padding: 4px;
1000
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1001
+ }
1002
+
1003
+ .trace-graph-zoom-btn {
1004
+ width: 32px;
1005
+ height: 32px;
1006
+ border: none;
1007
+ background: transparent;
1008
+ border-radius: 4px;
1009
+ cursor: pointer;
1010
+ display: flex;
1011
+ align-items: center;
1012
+ justify-content: center;
1013
+ color: #656d76;
1014
+ }
1015
+
1016
+ .trace-graph-zoom-btn:hover {
1017
+ background: #f6f8fa;
1018
+ color: #1f2328;
1019
+ }
1020
+ `;
1021
+ document.head.appendChild(style);
1022
+ })();
1023
+
1024
+
1025
+ // =============================================================================
1026
+ // Export for module usage
1027
+ // =============================================================================
1028
+
1029
+ if (typeof module !== 'undefined' && module.exports) {
1030
+ module.exports = { TracingGraphView, TracingNodeModal };
1031
+ }