shokupan 0.10.5 → 0.11.0

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 (54) hide show
  1. package/dist/{analyzer-CKLGLFtx.cjs → analyzer-BAhvpNY_.cjs} +2 -7
  2. package/dist/{analyzer-CKLGLFtx.cjs.map → analyzer-BAhvpNY_.cjs.map} +1 -1
  3. package/dist/{analyzer-BqIe1p0R.js → analyzer-CnKnQ5KV.js} +3 -8
  4. package/dist/{analyzer-BqIe1p0R.js.map → analyzer-CnKnQ5KV.js.map} +1 -1
  5. package/dist/{analyzer.impl-D9Yi1Hax.cjs → analyzer.impl-CfpMu4-g.cjs} +586 -40
  6. package/dist/analyzer.impl-CfpMu4-g.cjs.map +1 -0
  7. package/dist/{analyzer.impl-CV6W1Eq7.js → analyzer.impl-DCiqlXI5.js} +586 -40
  8. package/dist/analyzer.impl-DCiqlXI5.js.map +1 -0
  9. package/dist/cli.cjs +206 -18
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +206 -18
  12. package/dist/cli.js.map +1 -1
  13. package/dist/context.d.ts +6 -1
  14. package/dist/index.cjs +2339 -984
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +2336 -982
  17. package/dist/index.js.map +1 -1
  18. package/dist/plugins/application/api-explorer/static/explorer-client.mjs +375 -29
  19. package/dist/plugins/application/api-explorer/static/style.css +327 -8
  20. package/dist/plugins/application/api-explorer/static/theme.css +7 -2
  21. package/dist/plugins/application/asyncapi/generator.d.ts +4 -0
  22. package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +154 -22
  23. package/dist/plugins/application/asyncapi/static/style.css +24 -8
  24. package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +107 -0
  25. package/dist/plugins/application/dashboard/metrics-collector.d.ts +38 -2
  26. package/dist/plugins/application/dashboard/plugin.d.ts +44 -1
  27. package/dist/plugins/application/dashboard/static/charts.js +127 -62
  28. package/dist/plugins/application/dashboard/static/client.js +160 -0
  29. package/dist/plugins/application/dashboard/static/graph.mjs +167 -56
  30. package/dist/plugins/application/dashboard/static/reactflow.css +20 -10
  31. package/dist/plugins/application/dashboard/static/registry.js +112 -8
  32. package/dist/plugins/application/dashboard/static/requests.js +868 -58
  33. package/dist/plugins/application/dashboard/static/styles.css +186 -14
  34. package/dist/plugins/application/dashboard/static/tabs.js +44 -9
  35. package/dist/plugins/application/dashboard/static/theme.css +7 -2
  36. package/dist/plugins/application/openapi/analyzer.impl.d.ts +61 -1
  37. package/dist/plugins/application/openapi/openapi.d.ts +3 -0
  38. package/dist/plugins/application/shared/ast-utils.d.ts +7 -0
  39. package/dist/router.d.ts +55 -16
  40. package/dist/shokupan.d.ts +7 -2
  41. package/dist/util/adapter/adapters.d.ts +19 -0
  42. package/dist/util/adapter/filesystem.d.ts +20 -0
  43. package/dist/util/controller-scanner.d.ts +4 -0
  44. package/dist/util/cpu-monitor.d.ts +2 -0
  45. package/dist/util/middleware-tracker.d.ts +10 -0
  46. package/dist/util/types.d.ts +37 -0
  47. package/package.json +5 -5
  48. package/dist/analyzer.impl-CV6W1Eq7.js.map +0 -1
  49. package/dist/analyzer.impl-D9Yi1Hax.cjs.map +0 -1
  50. package/dist/http-server-BEMPIs33.cjs +0 -85
  51. package/dist/http-server-BEMPIs33.cjs.map +0 -1
  52. package/dist/http-server-CCeagTyU.js +0 -68
  53. package/dist/http-server-CCeagTyU.js.map +0 -1
  54. package/dist/plugins/application/dashboard/static/poll.js +0 -146
@@ -18,6 +18,10 @@ function renderPath(path) {
18
18
 
19
19
  let out = '';
20
20
  parts.forEach((part, index) => {
21
+ if (part === '.well-known') {
22
+ out += `/<span class="path-segment" style="color: #8b5cf6; font-weight: bold;">${part}</span>`;
23
+ return;
24
+ }
21
25
  if (part.startsWith(":")) {
22
26
  out += `/<span class="path-segment path-param">${part}</span>`;
23
27
  return;
@@ -35,8 +39,44 @@ function renderPath(path) {
35
39
  const GroupNode = ({ data }) => {
36
40
  return React.createElement('div', { style: { padding: '10px', height: '100%' } },
37
41
  React.createElement('div', { style: { fontWeight: 'bold', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '5px', marginBottom: '5px' } },
38
- data.type === "controller" ? data.label : "Router: " + data.label
42
+ data.type === "controller" ? data.label : (data.data.metadata?.pluginName ? data.label : "Router: " + data.label)
43
+ ),
44
+ /* Middleware Rendering */
45
+ (data.data?.children?.middleware || [])?.map((mw, i) =>
46
+ React.createElement('div', {
47
+ key: 'mw-' + i, style: {
48
+ background: '#7e22ce20', color: '#fff', padding: '2px 6px', borderRadius: '4px', fontSize: '10px', margin: '2px 0 4px 0', border: '1px solid #6b21a8',
49
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between'
50
+ }
51
+ },
52
+ React.createElement('span', { style: { fontWeight: 'bold' } }, "MW"),
53
+ React.createElement('span', { style: { font: 'var(--shokupan-font-mono)' } }, mw.name || "Middleware")
54
+ )
55
+ ),
56
+
57
+
58
+ /* Event Rendering */
59
+ (data.data?.children?.events || [])?.map((ev, i) =>
60
+ React.createElement('div', { key: 'ev-' + i, style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '12px', margin: '2px 0' } },
61
+ React.createElement('span', {
62
+ style: {
63
+ padding: '2px 6px',
64
+ borderRadius: '4px',
65
+ background: '#60a5fa20',
66
+ border: '1px solid #60a5fa',
67
+ color: 'white',
68
+ fontWeight: 'bold',
69
+ fontSize: '10px',
70
+ minWidth: '40px',
71
+ textAlign: 'center'
72
+ }
73
+ }, "WS"),
74
+ React.createElement('span', {
75
+ style: { font: 'var(--shokupan-font-mono)', color: '#cbd5e1' }
76
+ }, ev.name)
77
+ )
39
78
  ),
79
+
40
80
  data.data?.children?.routes?.map((r, i) =>
41
81
  React.createElement('div', { key: i, style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '12px', margin: '2px 0' } },
42
82
  React.createElement('span', {
@@ -60,7 +100,7 @@ const GroupNode = ({ data }) => {
60
100
  }
61
101
  }, r.method),
62
102
  React.createElement('span', {
63
- style: { fontFamily: 'monospace', color: r.isFailed ? '#ef4444' : '#cbd5e1', fontWeight: r.isFailed ? 'bold' : 'normal' },
103
+ style: { font: 'var(--shokupan-font-mono)', color: r.isFailed ? '#ef4444' : '#cbd5e1', fontWeight: r.isFailed ? 'bold' : 'normal' },
64
104
  dangerouslySetInnerHTML: { __html: renderPath(r.path) }
65
105
  })
66
106
  )
@@ -208,9 +248,59 @@ const GraphComponent = () => {
208
248
  // Label logic matches GroupNode
209
249
  header.textContent = container.type === "controller"
210
250
  ? (container.name + (container.metadata?.pluginName ? `\n[${container.metadata.pluginName}]` : ''))
211
- : "Router: " + container.path;
251
+ : (container.metadata?.pluginName ? `[${container.metadata.pluginName}]` : "Router: " + container.path);
212
252
  nodeEl.appendChild(header);
213
253
 
254
+ // Mimic Middleware
255
+ const middleware = container.children?.middleware || [];
256
+ for (const mw of middleware) {
257
+ const mwEl = document.createElement("div");
258
+ mwEl.style.display = "flex";
259
+ mwEl.style.alignItems = "center";
260
+ mwEl.style.justifyContent = "space-between";
261
+ mwEl.style.padding = "2px 6px";
262
+ mwEl.style.fontSize = "10px";
263
+ mwEl.style.margin = "2px 0 4px 0";
264
+ mwEl.style.border = "1px solid transparent"; // Match border in component
265
+
266
+ const label = document.createElement("span");
267
+ label.textContent = "MW";
268
+ label.style.fontWeight = "bold";
269
+ mwEl.appendChild(label);
270
+
271
+ const val = document.createElement("span");
272
+ val.textContent = mw.name || "Middleware";
273
+ val.style.font = "var(--shokupan-font-mono)";
274
+ mwEl.appendChild(val);
275
+
276
+ nodeEl.appendChild(mwEl);
277
+ nodeEl.appendChild(mwEl);
278
+ }
279
+
280
+ // Mimic Events
281
+ const events = container.children?.events || [];
282
+ for (const ev of events) {
283
+ const row = document.createElement("div");
284
+ row.style.display = "flex";
285
+ row.style.alignItems = "center";
286
+ row.style.gap = "8px";
287
+ row.style.margin = "2px 0";
288
+
289
+ const badge = document.createElement("span");
290
+ badge.textContent = "WS";
291
+ badge.style.padding = "2px 6px";
292
+ badge.style.fontSize = "10px";
293
+ badge.style.fontWeight = "bold";
294
+ row.appendChild(badge);
295
+
296
+ const name = document.createElement("span");
297
+ name.textContent = ev.name;
298
+ name.style.font = "var(--shokupan-font-mono)";
299
+ row.appendChild(name);
300
+
301
+ nodeEl.appendChild(row);
302
+ }
303
+
214
304
  // Mimic Routes
215
305
  for (const route of routes) {
216
306
  const row = document.createElement("div");
@@ -228,7 +318,7 @@ const GraphComponent = () => {
228
318
 
229
319
  const path = document.createElement("span");
230
320
  path.textContent = route.path;
231
- path.style.fontFamily = "monospace";
321
+ path.style.font = "var(--shokupan-font-mono)";
232
322
  // path.style.color... doesn't affect size
233
323
  row.appendChild(path);
234
324
 
@@ -250,34 +340,36 @@ const GraphComponent = () => {
250
340
 
251
341
  const elkEdges = [];
252
342
  const elkNodes = [
253
- ...(middleware || []).map((mw, idx) => {
254
- const id = makeId("middleware", parentId, idx, mw.name);
255
- return {
256
- id,
257
- label: mw.metadata?.pluginName || mw.name || "Unknown Middleware",
258
- ...calculateNodeBounds(mw),
259
- // width: 140,
260
- // height: 40,
261
- type: "middleware",
262
- style: getNodeStyle(id),
263
- data: mw
264
- };
265
- }),
343
+ // Middleware nodes removed
266
344
  ...(routers || []).map((r, idx) => {
267
345
  const id = makeId("router", parentId, idx, r.path);
268
346
  const { nodes, edges } = addRecursedLevel(r.children, id);
269
347
  restChildrenNodes.push(...nodes);
270
348
  restChildrenEdges.push(...edges);
271
349
 
350
+ const isPlugin = r.metadata?.pluginName;
351
+ const label = isPlugin
352
+ ? `[${r.metadata.pluginName}]`
353
+ : r.path;
354
+
355
+ const baseStyle = getNodeStyle(id);
356
+ const routerStyle = isPlugin
357
+ ? {
358
+ ...baseStyle,
359
+ background: '#f59e0b10',
360
+ color: '#f59e0b',
361
+ border: '1px solid #f59e0b'
362
+ }
363
+ : {
364
+ ...baseStyle
365
+ };
366
+
272
367
  return {
273
368
  id,
274
- label: r.path,
369
+ label: label,
275
370
  ...calculateNodeBounds(r),
276
371
  type: "router",
277
- style: {
278
- ...getNodeStyle(id),
279
- backgroundColor: 'red'
280
- },
372
+ style: routerStyle,
281
373
  data: r
282
374
  };
283
375
  }),
@@ -305,33 +397,12 @@ const GraphComponent = () => {
305
397
  };
306
398
  });
307
399
 
308
- let lastMiddlewareId = "";
309
- // Create middleware edges
310
- middleware?.forEach((mw, idx) => {
311
- const id = makeId("middleware", parentId, idx, mw.name);
312
-
313
- let sourceId = idx === 0 ?
314
- parentId ? parentId : "entrypoint-http"
315
- : makeId("middleware", parentId, idx - 1, middleware[idx - 1].name);
316
-
317
- elkEdges.push({
318
- id,
319
- sources: [sourceId],
320
- targets: [id],
321
- type: "straight",
322
- style: {
323
- ...getEdgeStyle(mw),
324
- backgroundColor: 'blue'
325
- }
326
- });
327
- lastMiddlewareId = id;
328
- });
329
-
400
+ // Simplified Edge Creation - Direct connections
330
401
  routers?.forEach((r, idx) => {
331
402
  const id = makeId("router", parentId, idx, r.path);
332
403
  elkEdges.push({
333
- id,
334
- sources: [lastMiddlewareId || parentId],
404
+ id: `edge_${parentId}_${id}`,
405
+ sources: [parentId],
335
406
  targets: [id],
336
407
  style: {
337
408
  ...getEdgeStyle(r),
@@ -341,10 +412,9 @@ const GraphComponent = () => {
341
412
  });
342
413
  controllers?.forEach((ctrl, idx) => {
343
414
  const id = makeId("controller", parentId, idx, ctrl.path);
344
- console.log({ id, lastMiddlewareId });
345
415
  elkEdges.push({
346
- id,
347
- sources: [lastMiddlewareId || parentId],
416
+ id: `edge_${parentId}_${id}`,
417
+ sources: [parentId],
348
418
  targets: [id],
349
419
  style: {
350
420
  ...getEdgeStyle(ctrl),
@@ -359,10 +429,48 @@ const GraphComponent = () => {
359
429
  return { nodes, edges };
360
430
  }
361
431
 
362
- const { nodes: elkNodes, edges: elkEdges } = addRecursedLevel(registryData);
363
- elkNodes.push({
364
- id: "entrypoint-http", width: 64, height: 64, type: "entrypoint"
365
- });
432
+ const rootNodeId = "router_root";
433
+ const rootNodeData = {
434
+ type: 'router',
435
+ metadata: { name: 'Root Application' },
436
+ path: '/',
437
+ children: {
438
+ routes: registryData.routes,
439
+ middleware: registryData.middleware,
440
+ events: registryData.events
441
+ }
442
+ };
443
+
444
+ // Pass rootNodeId as parent to children
445
+ const { nodes: childrenNodes, edges: childrenEdges } = addRecursedLevel(registryData, rootNodeId);
446
+
447
+ // Create explicit Root Node
448
+ const rootNode = {
449
+ id: rootNodeId,
450
+ label: "Root Application",
451
+ ...calculateNodeBounds(rootNodeData),
452
+ type: "router",
453
+ style: getNodeStyle(rootNodeId),
454
+ data: rootNodeData
455
+ };
456
+
457
+ // Combine all nodes
458
+ const elkNodes = [
459
+ rootNode,
460
+ ...childrenNodes,
461
+ { id: "entrypoint-http", width: 64, height: 64, type: "entrypoint" }
462
+ ];
463
+
464
+ // Connect Entrypoint -> Root
465
+ const entrypointEdge = {
466
+ id: 'edge_entrypoint_root',
467
+ sources: ['entrypoint-http'],
468
+ targets: [rootNodeId],
469
+ type: "straight",
470
+ style: { stroke: '#3b82f6' }
471
+ };
472
+
473
+ const elkEdges = [entrypointEdge, ...childrenEdges];
366
474
 
367
475
  const nodeNodeGap = '20';
368
476
  const nodeEdgeGap = '20';
@@ -378,6 +486,7 @@ const GraphComponent = () => {
378
486
  'elk.layered.spacing.edgeNodeBetweenLayers': nodeEdgeGap,
379
487
  'elk.layered.wrapping.additionalEdgeSpacing': nodeEdgeGap,
380
488
  'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',
489
+ 'elk.layered.nodePlacement.bk.fixedAlignment': 'LEFTUP',
381
490
  },
382
491
  children: elkNodes,
383
492
  edges: elkEdges
@@ -394,6 +503,7 @@ const GraphComponent = () => {
394
503
  }
395
504
 
396
505
  const style = NODE_STYLES[node.type] || {};
506
+ const customStyle = node.style || {};
397
507
  const isGroup = node.children && node.children.length > 0;
398
508
 
399
509
  flowNodes.push({
@@ -402,6 +512,7 @@ const GraphComponent = () => {
402
512
  data: node,
403
513
  style: {
404
514
  ...style,
515
+ ...customStyle,
405
516
  width: node.width,
406
517
  height: node.height,
407
518
  zIndex: isGroup ? -1 : 1
@@ -450,10 +561,10 @@ const GraphComponent = () => {
450
561
  width: '100vw',
451
562
  height: '100vh',
452
563
  zIndex: 9999,
453
- backgroundColor: '#1e293b' // Match theme
564
+ backgroundColor: 'var(--bg-primary)' // Match theme
454
565
  } : {
455
566
  width: '100%',
456
- height: '600px', // Ensure explicit height if container doesn't provide it, though normally #cy does.
567
+ height: '100%',
457
568
  position: 'relative'
458
569
  }
459
570
  },
@@ -487,8 +598,8 @@ const GraphComponent = () => {
487
598
  color: 'white',
488
599
  padding: '6px 12px',
489
600
  borderRadius: '6px',
601
+ font: 'var(--shokupan-font-mono)',
490
602
  fontSize: '12px',
491
- fontFamily: 'monospace',
492
603
  border: '1px solid #475569'
493
604
  }
494
605
  }, `${Math.round(zoom * 100)}%`),
@@ -1,13 +1,3 @@
1
- .react-flow__controls-button {
2
- color: #000 !important;
3
- fill: #000 !important;
4
- border-bottom: 1px solid #eee;
5
- }
6
-
7
- .react-flow__controls-button svg {
8
- fill: currentColor;
9
- }
10
-
11
1
  .react-flow__node.react-flow__node-router {
12
2
  height: fit-content !important;
13
3
  }
@@ -15,4 +5,24 @@
15
5
  .react-flow__handle {
16
6
  opacity: 0;
17
7
  pointer-events: none;
8
+ }
9
+
10
+ .react-flow__attribution {
11
+ background-color: var(--palette-dark-secondary);
12
+ border-top-left-radius: 4px;
13
+ }
14
+
15
+ .react-flow__attribution a {
16
+ color: #000;
17
+ font-weight: 600;
18
+ font-size: 14px;
19
+ }
20
+
21
+ .react-flow__controls {
22
+ --xy-controls-button-background-color: var(--bg-primary);
23
+ --xy-controls-button-border-color-props: #202020;
24
+ --xy-controls-button-color: var(--text-primary);
25
+
26
+ --xy-controls-button-color-hover: var(--palette-dark-primary);
27
+ --xy-controls-button-background-color-hover: var(--bg-primary);
18
28
  }
@@ -1,18 +1,81 @@
1
1
 
2
2
  window.renderRegistry = function renderRegistry(node, container) {
3
- const rootPath = "<%~ it.rootPath %>";
4
- const linkPattern = "<%~ it.linkPattern %>";
3
+ const config = window.SHOKUPAN_CONFIG || {};
4
+ const rootPath = config.rootPath || "";
5
+ const linkPattern = config.linkPattern || "vscode://file/{{absolute}}:{{line}}";
5
6
 
6
7
  if (!node) {
7
8
  container.innerHTML = '<div style="color: var(--text-secondary)">No registry data available</div>';
8
9
  return;
9
10
  }
10
11
 
12
+ // 0. Pre-process paths for shortening
13
+ // Collect all paths
14
+ const allFilePaths = new Set();
15
+ const collectPaths = (n) => {
16
+ if (!n) return;
17
+ if (n.metadata && n.metadata.file) allFilePaths.add(n.metadata.file);
18
+ if (n.middleware) n.middleware.forEach(collectPaths);
19
+ if (n.routers) n.routers.forEach(collectPaths);
20
+ if (n.controllers) n.controllers.forEach(collectPaths);
21
+ if (n.routes) n.routes.forEach(collectPaths);
22
+ if (n.events) n.events.forEach(collectPaths);
23
+ if (n.children) collectPaths(n.children); // recursive for routers
24
+ };
25
+ collectPaths(node);
26
+
27
+ // Compute Shortest Unique Paths
28
+ const shortPathMap = {};
29
+ const pathsByFilename = {};
30
+
31
+ allFilePaths.forEach(p => {
32
+ const parts = p.split('/');
33
+ const filename = parts.pop();
34
+ if (!pathsByFilename[filename]) pathsByFilename[filename] = [];
35
+ pathsByFilename[filename].push({ full: p, parts: parts });
36
+ });
37
+
38
+ Object.keys(pathsByFilename).forEach(filename => {
39
+ const group = pathsByFilename[filename];
40
+ if (group.length === 1) {
41
+ shortPathMap[group[0].full] = filename;
42
+ } else {
43
+ // Collision handling
44
+ group.forEach(item => {
45
+ let suffix = filename;
46
+ let depth = 0;
47
+ // Add parent dirs until unique within this group
48
+ // Note: This is a simple approach. Strictly we need to check uniqueness against ALL other paths in group.
49
+ while (true) {
50
+ if (depth >= item.parts.length) break; // Should not happen if they are different files
51
+
52
+ // Check if current suffix is unique in group
53
+ const conflicts = group.filter(g => {
54
+ if (g.full === item.full) return false;
55
+ // Construct suffix for g with same depth
56
+ const gParts = g.full.split('/');
57
+ const gSuffix = gParts.slice(gParts.length - 1 - depth).join('/');
58
+ return gSuffix === suffix;
59
+ });
60
+
61
+ if (conflicts.length === 0) break;
62
+
63
+ // prepend parent
64
+ const parent = item.parts[item.parts.length - 1 - depth];
65
+ suffix = parent + '/' + suffix;
66
+ depth++;
67
+ }
68
+ shortPathMap[item.full] = suffix;
69
+ });
70
+ }
71
+ });
72
+
11
73
  const wrapper = document.createElement('div');
12
74
 
13
75
  // Helper to clean paths
14
76
  const cleanPath = (p) => {
15
77
  if (!p) return '';
78
+ if (shortPathMap[p]) return shortPathMap[p];
16
79
  if (p.startsWith(rootPath)) return p.slice(rootPath.length + 1);
17
80
  return p;
18
81
  };
@@ -42,7 +105,13 @@ window.renderRegistry = function renderRegistry(node, container) {
42
105
  const builtin = metadata.isBuiltin ? `<span class="badge" style="background: #059669; margin-left:10px;">BUILTIN</span>` : '';
43
106
  const pluginName = metadata.pluginName ? `<span style="color: #6ee7b7; margin-left: 5px;">[${metadata.pluginName}]</span>` : '';
44
107
 
45
- return `<a href="${link}" target="_blank" style="text-decoration: none; color: inherit;"><span class="tree-meta" style="cursor: pointer; text-decoration: underline;">${relative}:${line}</span></a>${displayNameStr} ${builtin} ${pluginName}`;
108
+ // Use relative (short) path for display
109
+ return `<a href="${link}" style="text-decoration: none; color: inherit;">
110
+ <span class="tree-meta" style="cursor: pointer; text-decoration: underline;">
111
+ ${relative}:${line}</span>
112
+ </a>
113
+ ${!metadata.pluginName ? displayNameStr : ''} ${builtin} ${pluginName}
114
+ `;
46
115
  }
47
116
  return '';
48
117
  };
@@ -53,9 +122,10 @@ window.renderRegistry = function renderRegistry(node, container) {
53
122
  if (node.routes) node.routes.forEach(i => allItems.push({ ...i, kind: 'route' }));
54
123
  if (node.routers) node.routers.forEach(i => allItems.push({ ...i, kind: 'router' }));
55
124
  if (node.controllers) node.controllers.forEach(i => allItems.push({ ...i, kind: 'controller' }));
125
+ if (node.events) node.events.forEach(i => allItems.push({ ...i, kind: 'event' }));
56
126
 
57
127
  // 2. Sort by Order
58
- const kindPriority = { 'middleware': 0, 'router': 1, 'controller': 2, 'route': 3 };
128
+ const kindPriority = { 'middleware': 0, 'router': 1, 'controller': 2, 'route': 3, 'event': 4 };
59
129
  allItems.sort((a, b) => {
60
130
  const pA = kindPriority[a.kind] !== undefined ? kindPriority[a.kind] : 99;
61
131
  const pB = kindPriority[b.kind] !== undefined ? kindPriority[b.kind] : 99;
@@ -101,9 +171,9 @@ window.renderRegistry = function renderRegistry(node, container) {
101
171
  return `
102
172
  <div class="tooltip-text">
103
173
  <div style="font-weight:bold; margin-bottom:4px; border-bottom:1px solid var(--text-secondary); padding-bottom:2px;">Metrics</div>
104
- <div style="display:flex; justify-content:space-between;"><span>Requests:</span> <span style="font-family:monospace">${m.requests}</span></div>
105
- <div style="display:flex; justify-content:space-between;"><span>Traffic:</span> <span style="font-family:monospace">${percent}%</span></div>
106
- <div style="display:flex; justify-content:space-between;"><span>Failures:</span> <span style="font-family:monospace; color:${m.failures > 0 ? '#ef4444' : 'inherit'}">${m.failures} (${failRate}%)</span></div>
174
+ <div style="display:flex; justify-content:space-between;"><span>Requests:</span> <span style="font: var(--shokupan-font-mono)">${m.requests}</span></div>
175
+ <div style="display:flex; justify-content:space-between;"><span>Traffic:</span> <span style="font: var(--shokupan-font-mono)">${percent}%</span></div>
176
+ <div style="display:flex; justify-content:space-between;"><span>Failures:</span> <span style="font: var(--shokupan-font-mono); color:${m.failures > 0 ? '#ef4444' : 'inherit'}">${m.failures} (${failRate}%)</span></div>
107
177
  </div>
108
178
  `;
109
179
  }
@@ -113,6 +183,10 @@ window.renderRegistry = function renderRegistry(node, container) {
113
183
 
114
184
  let out = '';
115
185
  parts.forEach((part, index) => {
186
+ if (part === '.well-known') {
187
+ out += `/<span class="path-segment" style="color: #8b5cf6; font-weight: bold;">${part}</span>`;
188
+ return;
189
+ }
116
190
  if (part.startsWith(":")) {
117
191
  out += `/<span class="path-segment path-param">${part}</span>`;
118
192
  return;
@@ -152,8 +226,18 @@ window.renderRegistry = function renderRegistry(node, container) {
152
226
  header.className = 'tree-item tooltip'; // Add tooltip class
153
227
  const meta = createFileMeta(item.metadata, 'Router');
154
228
  const tooltipHtml = getTooltipHtml(item.id);
229
+ const isPlugin = item.metadata && item.metadata.pluginName;
230
+ const badgeLabel = isPlugin ? 'PLUGIN' : 'ROUTER';
231
+ const badgeClass = isPlugin ? 'badge-PLUGIN' : 'badge-ROUTER';
232
+
233
+ // Add custom style for PLUGIN badge if needed, or rely on generic badge class + modifier
234
+ // For now, let's inject a style for badge-PLUGIN if it doesn't exist, or just inline it.
235
+ // Actually, let's just use inline style for the distinctive color if it's a plugin
236
+ // to ensure it stands out without editing CSS file.
237
+ const badgeStyle = isPlugin ? 'background: #f59e0b; color: #000;' : '';
238
+
155
239
  header.innerHTML = `
156
- <span class="badge badge-ROUTER">ROUTER</span>
240
+ <span class="badge ${badgeClass}" style="${badgeStyle}">${badgeLabel}</span>
157
241
  <span class="tree-label">${renderPath(item.path)}</span>
158
242
  ${meta}
159
243
  ${tooltipHtml}
@@ -243,6 +327,26 @@ window.renderRegistry = function renderRegistry(node, container) {
243
327
  `;
244
328
  wrapper.appendChild(div);
245
329
  }
330
+
331
+ // Event
332
+ else if (item.kind === 'event') {
333
+ const div = document.createElement('div');
334
+ div.className = 'tree-item tooltip'; // Add tooltip class
335
+
336
+ // Event Badge style
337
+ const badgeStyle = "width: 50px; text-align: center; display: inline-block;";
338
+
339
+ const meta = createFileMeta(item.metadata, item.handlerName);
340
+ const tHtml = getTooltipHtml(item.id);
341
+
342
+ div.innerHTML = `
343
+ <span class="badge badge-SEND" style="${badgeStyle}">WS</span>
344
+ <span class="tree-label">${item.name}</span>
345
+ ${meta}
346
+ ${tHtml}
347
+ `;
348
+ wrapper.appendChild(div);
349
+ }
246
350
  });
247
351
  container.innerHTML = '';
248
352
  container.appendChild(wrapper);