@anlyx/ui 0.1.2 → 0.1.3

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 (43) hide show
  1. package/dist/components/AnalysisEvidenceList.d.ts +5 -0
  2. package/dist/components/AnalysisEvidenceList.js +61 -0
  3. package/dist/components/AnlyxAppShell.d.ts +1 -1
  4. package/dist/components/AnlyxAppShell.js +16 -7
  5. package/dist/components/ApiCallList.d.ts +3 -2
  6. package/dist/components/ApiCallList.js +12 -2
  7. package/dist/components/EndpointMapCanvas.js +1 -1
  8. package/dist/components/FlowStoryView.d.ts +22 -0
  9. package/dist/components/FlowStoryView.js +117 -0
  10. package/dist/components/InspectorPanel.d.ts +1 -1
  11. package/dist/components/InspectorPanel.js +20 -1
  12. package/dist/components/PageStoryboardView.js +9 -1
  13. package/dist/components/ProcessFlowView.js +8 -1
  14. package/dist/components/ReplayControls.d.ts +2 -1
  15. package/dist/components/ReplayControls.js +29 -2
  16. package/dist/components/Sidebar.d.ts +2 -2
  17. package/dist/components/Sidebar.js +15 -3
  18. package/dist/mock-data.js +50 -4
  19. package/dist/overlay/AnlyxFlowEdge.d.ts +2 -0
  20. package/dist/overlay/AnlyxFlowEdge.js +15 -0
  21. package/dist/overlay/AnlyxFlowNode.d.ts +13 -0
  22. package/dist/overlay/AnlyxFlowNode.js +28 -0
  23. package/dist/overlay/FlowDrawer.d.ts +2 -0
  24. package/dist/overlay/FlowDrawer.js +59 -0
  25. package/dist/overlay/MainFlowCanvas.d.ts +20 -0
  26. package/dist/overlay/MainFlowCanvas.js +285 -0
  27. package/dist/overlay/RecentApiEventsTable.d.ts +5 -0
  28. package/dist/overlay/RecentApiEventsTable.js +19 -0
  29. package/dist/overlay/overlay-entry.d.ts +8 -0
  30. package/dist/overlay/overlay-entry.js +14 -0
  31. package/dist/overlay/overlay-ui.css +2 -0
  32. package/dist/overlay/overlay-ui.js +14 -0
  33. package/dist/overlay/types.d.ts +38 -0
  34. package/dist/overlay/types.js +1 -0
  35. package/dist/overlay/ui.d.ts +18 -0
  36. package/dist/overlay/ui.js +13 -0
  37. package/dist/readme-demo/ReadmeDemoApp.d.ts +4 -0
  38. package/dist/readme-demo/ReadmeDemoApp.js +126 -0
  39. package/dist/readme-demo/readme-demo-entry.d.ts +1 -0
  40. package/dist/readme-demo/readme-demo-entry.js +8 -0
  41. package/dist/styles.css +1134 -20
  42. package/dist/viewer/ViewerApp.js +13 -6
  43. package/package.json +3 -3
package/dist/mock-data.js CHANGED
@@ -36,7 +36,15 @@ export const mockScanResult = scanResultSchema.parse({
36
36
  id: "endpoint:get:/api/public/benefits/{id}",
37
37
  type: "endpoint",
38
38
  label: "GET /api/public/benefits/{id}",
39
- confidence: "high"
39
+ confidence: "high",
40
+ evidence: [
41
+ {
42
+ label: "Matched Spring mapping",
43
+ detail: '@GetMapping("/api/public/benefits/{id}")',
44
+ source: "spring-endpoint-scanner",
45
+ confidence: "high"
46
+ }
47
+ ]
40
48
  },
41
49
  {
42
50
  id: "controller:PublicBenefitController#getDetail",
@@ -44,25 +52,63 @@ export const mockScanResult = scanResultSchema.parse({
44
52
  label: "PublicBenefitController#getDetail",
45
53
  filePath: "backend/src/main/java/com/zup/benefit/PublicBenefitController.java",
46
54
  lineNumber: 24,
47
- confidence: "unknown"
55
+ confidence: "unknown",
56
+ evidence: [
57
+ {
58
+ label: "Controller method detected",
59
+ detail: "PublicBenefitController#getDetail",
60
+ source: "spring-flow-scanner",
61
+ confidence: "unknown"
62
+ }
63
+ ]
48
64
  },
49
65
  {
50
66
  id: "service:PublicBenefitService#getBenefitDetail",
51
67
  type: "service",
52
68
  label: "PublicBenefitService#getBenefitDetail",
53
- confidence: "high"
69
+ confidence: "high",
70
+ evidence: [
71
+ {
72
+ label: "Matched from controller field call",
73
+ detail: "publicBenefitService.getBenefitDetail(...)",
74
+ source: "spring-flow-scanner",
75
+ confidence: "high"
76
+ },
77
+ {
78
+ label: "Resolved service class by field type",
79
+ detail: "PublicBenefitService",
80
+ source: "spring-flow-scanner",
81
+ confidence: "high"
82
+ }
83
+ ]
54
84
  },
55
85
  {
56
86
  id: "repository:BenefitRepository#findById",
57
87
  type: "repository",
58
88
  label: "BenefitRepository#findById",
59
- confidence: "high"
89
+ confidence: "high",
90
+ evidence: [
91
+ {
92
+ label: "Repository call detected in method body",
93
+ detail: "benefitRepository.findById(...)",
94
+ source: "spring-flow-scanner",
95
+ confidence: "high"
96
+ }
97
+ ]
60
98
  },
61
99
  {
62
100
  id: "database:benefits",
63
101
  type: "database",
64
102
  label: "benefits",
65
103
  confidence: "high",
104
+ evidence: [
105
+ {
106
+ label: "Resolved repository entity",
107
+ detail: "Benefit -> benefits",
108
+ source: "spring-flow-scanner",
109
+ confidence: "high"
110
+ }
111
+ ],
66
112
  metadata: {
67
113
  tableName: "benefits"
68
114
  }
@@ -0,0 +1,2 @@
1
+ import { type EdgeProps } from "@xyflow/react";
2
+ export declare function AnlyxFlowEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, markerEnd, data }: EdgeProps): JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { BaseEdge, getSmoothStepPath } from "@xyflow/react";
3
+ export function AnlyxFlowEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, markerEnd, data }) {
4
+ const [edgePath] = getSmoothStepPath({
5
+ sourceX,
6
+ sourceY,
7
+ sourcePosition,
8
+ targetX,
9
+ targetY,
10
+ targetPosition,
11
+ borderRadius: 14
12
+ });
13
+ const tone = (data?.tone ?? "blue").toLowerCase();
14
+ return (_jsx(BaseEdge, { id: id, className: `anlyx-flow-rf-edge anlyx-flow-rf-edge--${tone}`, path: edgePath, ...(markerEnd ? { markerEnd } : {}) }));
15
+ }
@@ -0,0 +1,13 @@
1
+ import { type NodeProps } from "@xyflow/react";
2
+ export type AnlyxFlowNodeData = {
3
+ kind: "api" | "controller" | "service" | "repository" | "database" | "auth" | "result";
4
+ label: string;
5
+ value: string;
6
+ sub?: string;
7
+ badge: string;
8
+ accent: "blue" | "green" | "amber" | "violet" | "gray";
9
+ fullValue?: string;
10
+ step?: string;
11
+ state?: "taken" | "blocked" | "scanned";
12
+ };
13
+ export declare function AnlyxFlowNode({ data }: NodeProps): JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Handle, Position } from "@xyflow/react";
3
+ import { Box, Code2, Database, Globe2, Layers3, LockKeyhole, ShieldCheck } from "lucide-react";
4
+ import { Badge, Card, Tooltip } from "./ui.js";
5
+ export function AnlyxFlowNode({ data }) {
6
+ const nodeData = data;
7
+ const Icon = getIcon(nodeData.kind);
8
+ const state = nodeData.state ?? "taken";
9
+ return (_jsxs(Card, { className: `anlyx-flow-rf-node anlyx-flow-rf-node--${nodeData.accent} anlyx-flow-rf-node--${state}`, children: [_jsx(Handle, { className: "anlyx-flow-rf-handle", position: Position.Left, type: "target" }), _jsxs("div", { className: "anlyx-flow-rf-node__top", children: [_jsx("span", { className: "anlyx-flow-rf-node__icon", "aria-hidden": "true", children: _jsx(Icon, { size: 14, strokeWidth: 2.25 }) }), _jsx("span", { className: "anlyx-flow-rf-node__label", children: nodeData.label }), nodeData.step ? _jsx("span", { className: "anlyx-flow-rf-node__step", children: nodeData.step }) : null] }), _jsx(Tooltip, { content: nodeData.fullValue ?? nodeData.value, children: _jsx("p", { className: "anlyx-flow-rf-node__value", children: nodeData.value }) }), nodeData.sub ? _jsx("p", { className: "anlyx-flow-rf-node__sub", children: nodeData.sub }) : null, _jsx(Badge, { tone: nodeData.accent === "violet" ? "violet" : nodeData.accent, children: nodeData.badge }), _jsx(Handle, { className: "anlyx-flow-rf-handle", position: Position.Right, type: "source" })] }));
10
+ }
11
+ function getIcon(kind) {
12
+ switch (kind) {
13
+ case "api":
14
+ return Globe2;
15
+ case "controller":
16
+ return Code2;
17
+ case "service":
18
+ return Layers3;
19
+ case "repository":
20
+ return Box;
21
+ case "database":
22
+ return Database;
23
+ case "auth":
24
+ return LockKeyhole;
25
+ case "result":
26
+ return ShieldCheck;
27
+ }
28
+ }
@@ -0,0 +1,2 @@
1
+ import type { FlowDrawerProps } from "./types.js";
2
+ export declare function FlowDrawer({ selectedEvent, events, latestAction, scannedHints, loadError }: FlowDrawerProps): JSX.Element;
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Badge, Card } from "./ui.js";
3
+ import { MainFlowCanvas } from "./MainFlowCanvas.js";
4
+ import { RecentApiEventsTable } from "./RecentApiEventsTable.js";
5
+ export function FlowDrawer({ selectedEvent, events, latestAction, scannedHints = [], loadError }) {
6
+ if (loadError) {
7
+ return (_jsx("div", { className: "anlyx-flow-drawer-body", children: _jsxs(Card, { children: [_jsx("h3", { className: "anlyx-ov-section-title", children: "Report data" }), _jsx("div", { className: "anlyx-ov-empty", children: loadError })] }) }));
8
+ }
9
+ if (!selectedEvent) {
10
+ return (_jsxs("div", { className: "anlyx-flow-drawer-body", children: [latestAction ? _jsx(NoPrimaryRequest, { latestAction: latestAction }) : _jsx(WaitingForAction, {}), scannedHints.length > 0 ? _jsx(ScannedHints, { hints: scannedHints }) : null, _jsx(RecentApiEventsTable, { events: events, selectedEventId: null })] }));
11
+ }
12
+ return (_jsxs("div", { className: "anlyx-flow-drawer-body", children: [_jsx(CapturedRequest, { event: selectedEvent }), selectedEvent.matchedEndpoint ? (_jsx(MainFlowCanvas, { flow: selectedEvent.matchedFlow, method: selectedEvent.method, path: selectedEvent.path, status: selectedEvent.status, ...(selectedEvent.matchedEndpoint.confidence
13
+ ? { endpointConfidence: selectedEvent.matchedEndpoint.confidence }
14
+ : {}) })) : (_jsxs(Card, { className: "anlyx-unmatched-card", children: [_jsx("h3", { children: "No scanned endpoint matched" }), _jsx("p", { children: "Anlyx saw this browser request, but no scanned endpoint path matched it." })] })), _jsx(RecentApiEventsTable, { events: events, selectedEventId: selectedEvent.id })] }));
15
+ }
16
+ function ScannedHints({ hints }) {
17
+ return (_jsxs(Card, { className: "anlyx-scanned-hints", children: [_jsxs("div", { className: "anlyx-scanned-hints__head", children: [_jsxs("div", { children: [_jsx("h3", { children: "Scanned / inferred hints" }), _jsx("p", { children: "These are known page links, not browser-live requests." })] }), _jsx(Badge, { tone: "gray", children: "Not browser-live" })] }), _jsx("div", { className: "anlyx-scanned-hints__list", children: hints.slice(0, 4).map((hint, index) => (_jsxs("div", { className: "anlyx-scanned-hint", children: [_jsxs("div", { children: [_jsx(Badge, { tone: hint.evidence === "capture" ? "green" : "violet", children: hint.evidence === "capture" ? "captured page link" : "scanned page link" }), _jsx("strong", { title: hint.endpointLabel ?? `${hint.method} ${hint.path}`, children: hint.endpointLabel ?? `${hint.method} ${hint.path}` })] }), _jsx("p", { title: hint.pageFilePath ?? hint.pageRoute, children: hint.pageRoute })] }, `${hint.pageRoute}:${hint.method}:${hint.path}:${index}`))) })] }));
18
+ }
19
+ function WaitingForAction() {
20
+ return (_jsxs(Card, { children: [_jsx("h3", { className: "anlyx-ov-section-title", children: "Waiting" }), _jsx("div", { className: "anlyx-ov-empty", children: "Use the app normally. Anlyx opens the main flow for requests caused by your direct action. Background requests stay in Recent API events until you select them." })] }));
21
+ }
22
+ function NoPrimaryRequest({ latestAction }) {
23
+ return (_jsxs(Card, { className: "anlyx-no-primary-card", children: [_jsxs("div", { children: [_jsx(Badge, { tone: "amber", children: "No action API yet" }), _jsx("h3", { children: formatAction(latestAction) }), _jsx("p", { children: latestAction.selector ?? "No stable selector captured" })] }), _jsx("div", { className: "anlyx-no-primary-card__note", children: "Only background session checks were detected. They are hidden from the main flow." })] }));
24
+ }
25
+ function CapturedRequest({ event }) {
26
+ const matched = Boolean(event.matchedEndpoint);
27
+ return (_jsxs(Card, { className: "anlyx-captured-request", children: [_jsxs("div", { className: "anlyx-captured-request__top", children: [_jsxs("div", { children: [_jsx("h3", { children: "Captured request" }), _jsxs("div", { className: "anlyx-captured-request__path", children: [_jsx(Badge, { tone: "blue", children: event.method }), _jsx("strong", { children: event.path })] })] }), _jsx(Badge, { tone: matched ? "green" : "amber", children: matched ? "matched" : "unmatched" })] }), _jsxs("div", { className: "anlyx-captured-request__summary", children: [_jsxs("span", { children: [_jsx("b", { children: event.matchedEndpoint ? "endpoint" : "browser" }), " ", event.matchedEndpoint
28
+ ? `${event.matchedEndpoint.method} ${event.matchedEndpoint.path}`
29
+ : event.path] }), _jsx("span", { children: _jsx("b", { children: getStatusLabel(event.status) }) }), _jsxs("span", { children: [event.durationMs, "ms", event.count && event.count > 1 ? ` · seen x${event.count}` : ""] }), event.matchedEndpoint?.confidence ? (_jsxs("span", { children: ["confidence ", event.matchedEndpoint.confidence] })) : null, _jsx("span", { children: event.triggeredBy
30
+ ? "user action"
31
+ : event.source === "health"
32
+ ? "health/background"
33
+ : "background" })] }), _jsxs("div", { className: "anlyx-captured-request__steps", children: [_jsx(CapturedStep, { label: "Action", title: event.triggeredBy ? formatAction(event.triggeredBy) : "No user action captured", detail: event.triggeredBy?.selector ?? "Request may have fired on page load", tone: event.triggeredBy ? "blue" : "amber" }), _jsx(CapturedStep, { label: "Request", title: `${event.method} ${event.path}`, detail: matched ? "Matched scanned endpoint" : "No scanned endpoint matched", tone: matched ? "green" : "amber" }), _jsx(CapturedStep, { label: "Result", title: getStatusLabel(event.status), detail: `${event.durationMs}ms${event.count && event.count > 1 ? ` · seen x${event.count}` : ""}`, tone: Number(event.status) >= 400 ? "amber" : "green" })] })] }));
34
+ }
35
+ function CapturedStep({ label, title, detail, tone }) {
36
+ return (_jsxs("div", { className: `anlyx-captured-step anlyx-captured-step--${tone}`, children: [_jsx("span", { className: "anlyx-captured-step__dot" }), _jsxs("div", { children: [_jsx("p", { children: label }), _jsx("strong", { children: title }), _jsx("span", { children: detail })] })] }));
37
+ }
38
+ function formatAction(action) {
39
+ return `${action.type ?? "Action"} ${action.label ?? "unnamed element"}`;
40
+ }
41
+ function getStatusLabel(status) {
42
+ const numeric = Number(status);
43
+ if (numeric === 401) {
44
+ return "login required · 401";
45
+ }
46
+ if (numeric === 403) {
47
+ return "permission denied · 403";
48
+ }
49
+ if (numeric >= 500) {
50
+ return `server error · ${status}`;
51
+ }
52
+ if (numeric >= 400) {
53
+ return `client error · ${status}`;
54
+ }
55
+ if (numeric >= 200) {
56
+ return `success · ${status}`;
57
+ }
58
+ return `status ${status}`;
59
+ }
@@ -0,0 +1,20 @@
1
+ import type { EndpointFlow } from "@anlyx/core";
2
+ import { type Edge, type Node } from "@xyflow/react";
3
+ import { type AnlyxFlowNodeData } from "./AnlyxFlowNode.js";
4
+ export type MainFlowCanvasProps = {
5
+ flow: EndpointFlow | null | undefined;
6
+ method: string;
7
+ path: string;
8
+ status: string | number;
9
+ endpointConfidence?: string;
10
+ };
11
+ export declare function MainFlowCanvas({ flow, method, path, status, endpointConfidence }: MainFlowCanvasProps): JSX.Element;
12
+ export declare function buildDrawerFlowModel({ flow, method, path, status }: {
13
+ flow: EndpointFlow | null | undefined;
14
+ method: string;
15
+ path: string;
16
+ status: string | number;
17
+ }): {
18
+ nodes: Node<AnlyxFlowNodeData>[];
19
+ edges: Edge[];
20
+ };
@@ -0,0 +1,285 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Background, BackgroundVariant, MarkerType, Panel, ReactFlow, useReactFlow } from "@xyflow/react";
3
+ import { useCallback, useMemo } from "react";
4
+ import { AnlyxFlowEdge } from "./AnlyxFlowEdge.js";
5
+ import { AnlyxFlowNode } from "./AnlyxFlowNode.js";
6
+ import { Badge } from "./ui.js";
7
+ const nodeTypes = {
8
+ anlyxFlowNode: AnlyxFlowNode
9
+ };
10
+ const edgeTypes = {
11
+ anlyxFlowEdge: AnlyxFlowEdge
12
+ };
13
+ const NODE_X_GAP = 224;
14
+ const MAIN_Y = 76;
15
+ const AUTH_Y = 54;
16
+ const SUPPORT_Y = 196;
17
+ export function MainFlowCanvas({ flow, method, path, status, endpointConfidence }) {
18
+ const model = useMemo(() => buildDrawerFlowModel({ flow, method, path, status }), [flow, method, path, status]);
19
+ if (model.nodes.length === 0) {
20
+ return (_jsxs("section", { className: "anlyx-flow-rf-section", children: [_jsx("div", { className: "anlyx-flow-rf-head", children: _jsx("h3", { children: "Matched backend flow" }) }), _jsx("div", { className: "anlyx-flow-rf-empty", children: "No scanned main flow was inferred yet." })] }));
21
+ }
22
+ return (_jsxs("section", { className: "anlyx-flow-rf-section", children: [_jsxs("div", { className: "anlyx-flow-rf-head", children: [_jsxs("div", { children: [_jsx("h3", { children: "Matched backend flow" }), _jsx("p", { children: "Live path first, scanned downstream stays muted." })] }), _jsxs("div", { className: "anlyx-flow-rf-head__badges", children: [_jsx(Badge, { tone: "blue", children: method }), _jsxs(Badge, { tone: "green", children: ["confidence ", endpointConfidence ?? "unknown"] })] })] }), _jsx("div", { className: "anlyx-flow-rf-canvas", "data-testid": "anlyx-react-flow-main", children: _jsxs(ReactFlow, { edgeTypes: edgeTypes, edges: model.edges, elementsSelectable: false, fitView: true, fitViewOptions: { padding: 0.16 }, maxZoom: 1.35, minZoom: 0.58, nodes: model.nodes, nodesConnectable: false, nodesDraggable: false, nodeTypes: nodeTypes, panOnDrag: true, proOptions: { hideAttribution: true }, zoomOnDoubleClick: false, zoomOnPinch: true, zoomOnScroll: true, children: [_jsx(ViewportControls, {}), _jsx(Background, { color: "rgba(148, 163, 184, .42)", gap: 18, size: 1, variant: BackgroundVariant.Dots })] }) }), _jsx("p", { className: "anlyx-flow-rf-note", children: "Anlyx mapped this browser-visible request to the scanned backend flow. Muted nodes are known code paths; framework server-side fetches, such as Next.js server component data loading, require the scanned path until a server runtime bridge is enabled." })] }));
23
+ }
24
+ function ViewportControls() {
25
+ const { fitView, setViewport } = useReactFlow();
26
+ const fit = useCallback(() => {
27
+ void fitView({ padding: 0.18, duration: 160 });
28
+ }, [fitView]);
29
+ const reset = useCallback(() => {
30
+ void setViewport({ x: 20, y: 22, zoom: 0.82 }, { duration: 160 });
31
+ }, [setViewport]);
32
+ return (_jsxs(Panel, { className: "anlyx-flow-rf-controls", position: "top-right", children: [_jsx("button", { type: "button", onClick: fit, title: "Fit the full flow into view", children: "Fit view" }), _jsx("button", { type: "button", onClick: reset, title: "Reset to the default canvas position", children: "Reset view" })] }));
33
+ }
34
+ export function buildDrawerFlowModel({ flow, method, path, status }) {
35
+ const isAuthBlocked = Number(status) === 401 || Number(status) === 403;
36
+ const mainNodes = getMainNodes(flow);
37
+ const controller = mainNodes.find((node) => node.type === "controller");
38
+ const supportNodes = mainNodes.filter((item) => item.type !== "endpoint" && item.type !== "controller");
39
+ const nodes = [];
40
+ const edges = [];
41
+ nodes.push(createFlowNode("api", 0, MAIN_Y, {
42
+ kind: "api",
43
+ label: "API",
44
+ value: `${method} ${path}`,
45
+ badge: "high",
46
+ accent: "blue",
47
+ fullValue: `${method} ${path}`,
48
+ step: "01",
49
+ state: "taken"
50
+ }));
51
+ if (controller) {
52
+ nodes.push(createFlowNode("controller", NODE_X_GAP, MAIN_Y, {
53
+ kind: "controller",
54
+ label: "Controller",
55
+ value: compactHandlerName(controller.label),
56
+ sub: compactHandlerClassName(controller.label),
57
+ badge: controller.confidence ?? "unknown",
58
+ accent: "blue",
59
+ fullValue: controller.label,
60
+ step: "02",
61
+ state: "taken"
62
+ }));
63
+ }
64
+ if (isAuthBlocked) {
65
+ const authX = controller ? NODE_X_GAP * 2 : NODE_X_GAP;
66
+ const resultX = controller ? NODE_X_GAP * 3 : NODE_X_GAP * 2;
67
+ nodes.push(createFlowNode("auth", authX, AUTH_Y, {
68
+ kind: "auth",
69
+ label: "Auth / Session",
70
+ value: "Auth gate inferred",
71
+ sub: Number(status) === 403 ? "Likely permission gate" : "Likely login gate",
72
+ badge: `inferred ${status}`,
73
+ accent: "violet",
74
+ fullValue: "Inferred from the browser 401/403 result, not a runtime server trace.",
75
+ step: controller ? "03" : "02",
76
+ state: "taken"
77
+ }));
78
+ nodes.push(createFlowNode("result", resultX, AUTH_Y, getResultNodeData(status, { step: controller ? "04" : "03" })));
79
+ supportNodes.forEach((node, index) => {
80
+ nodes.push(createFlowNode(`support-${index}`, authX + index * NODE_X_GAP, SUPPORT_Y, toNodeData(node, true)));
81
+ });
82
+ edges.push(createFlowEdge("api-controller", "api", controller ? "controller" : "auth", "blue", true));
83
+ if (controller) {
84
+ edges.push(createFlowEdge("controller-auth", "controller", "auth", "violet", true));
85
+ }
86
+ edges.push(createFlowEdge("auth-result", "auth", "result", "amber", true));
87
+ supportNodes.forEach((_, index) => {
88
+ edges.push(createFlowEdge(`support-${index}`, index === 0 ? (controller ? "controller" : "api") : `support-${index - 1}`, `support-${index}`, "gray", false));
89
+ });
90
+ return { nodes, edges };
91
+ }
92
+ supportNodes.forEach((node, index) => {
93
+ nodes.push(createFlowNode(`support-${index}`, (index + (controller ? 2 : 1)) * NODE_X_GAP, MAIN_Y, toNodeData(node)));
94
+ });
95
+ nodes.push(createFlowNode("result", (supportNodes.length + (controller ? 2 : 1)) * NODE_X_GAP, MAIN_Y, getResultNodeData(status, {
96
+ step: String(supportNodes.length + (controller ? 3 : 2)).padStart(2, "0")
97
+ })));
98
+ nodes.slice(0, -1).forEach((node, index) => {
99
+ const target = nodes[index + 1];
100
+ edges.push(createFlowEdge(`main-${index}`, node.id, target.id, getEdgeTone(index, target.data.kind), true));
101
+ });
102
+ return { nodes, edges };
103
+ }
104
+ function createFlowNode(id, x, y, data) {
105
+ return {
106
+ id,
107
+ type: "anlyxFlowNode",
108
+ position: { x, y },
109
+ data
110
+ };
111
+ }
112
+ function createFlowEdge(id, source, target, tone, animated) {
113
+ return {
114
+ id: `drawer-edge-${id}`,
115
+ source,
116
+ target,
117
+ type: "anlyxFlowEdge",
118
+ animated,
119
+ markerEnd: {
120
+ type: MarkerType.ArrowClosed,
121
+ width: 10,
122
+ height: 10,
123
+ color: getEdgeColor(tone)
124
+ },
125
+ data: { tone }
126
+ };
127
+ }
128
+ function getResultNodeData(status, options = {}) {
129
+ return {
130
+ kind: "result",
131
+ label: "Result",
132
+ value: `${status} ${getStatusShortLabel(status)}`,
133
+ sub: Number(status) >= 400 ? "Request blocked" : "Request completed",
134
+ badge: Number(status) >= 400 ? "blocked" : "observed",
135
+ accent: Number(status) >= 400 ? "amber" : "green",
136
+ fullValue: getStatusLabel(status),
137
+ ...(options.step ? { step: options.step } : {}),
138
+ state: Number(status) >= 400 ? "blocked" : "taken"
139
+ };
140
+ }
141
+ function getMainNodes(flow) {
142
+ if (!flow || !Array.isArray(flow.mainPath)) {
143
+ return [];
144
+ }
145
+ const byId = new Map(flow.nodes.map((node) => [node.id, node]));
146
+ return flow.mainPath.map((id) => byId.get(id)).filter((node) => Boolean(node));
147
+ }
148
+ function toNodeData(node, blockedByAuth = false) {
149
+ const kind = getKind(node.type);
150
+ const sub = blockedByAuth
151
+ ? `Scanned ${getNodeSub(kind)?.toLowerCase() ?? "step"}`
152
+ : getNodeSub(kind);
153
+ return {
154
+ kind,
155
+ label: getNodeLabel(kind),
156
+ value: compactHandlerName(node.label),
157
+ badge: blockedByAuth ? "scanned" : (node.confidence ?? "unknown"),
158
+ accent: blockedByAuth ? "gray" : getNodeAccent(kind),
159
+ fullValue: node.label,
160
+ state: blockedByAuth ? "scanned" : "taken",
161
+ ...(sub ? { sub } : {})
162
+ };
163
+ }
164
+ function getKind(type) {
165
+ if (type === "service") {
166
+ return "service";
167
+ }
168
+ if (type === "repository") {
169
+ return "repository";
170
+ }
171
+ if (type === "database") {
172
+ return "database";
173
+ }
174
+ return "service";
175
+ }
176
+ function getNodeLabel(kind) {
177
+ if (kind === "repository") {
178
+ return "Repository";
179
+ }
180
+ if (kind === "database") {
181
+ return "Database";
182
+ }
183
+ return "Service";
184
+ }
185
+ function getNodeSub(kind) {
186
+ if (kind === "repository") {
187
+ return "Data access";
188
+ }
189
+ if (kind === "database") {
190
+ return "Persistence";
191
+ }
192
+ return "Business logic";
193
+ }
194
+ function getNodeAccent(kind) {
195
+ if (kind === "database") {
196
+ return "green";
197
+ }
198
+ if (kind === "repository") {
199
+ return "amber";
200
+ }
201
+ return "violet";
202
+ }
203
+ function getEdgeTone(index, targetKind) {
204
+ if (targetKind === "auth") {
205
+ return "violet";
206
+ }
207
+ if (targetKind === "result") {
208
+ return "amber";
209
+ }
210
+ return index === 0 ? "blue" : "violet";
211
+ }
212
+ function getEdgeColor(tone) {
213
+ if (tone === "amber") {
214
+ return "#f59e0b";
215
+ }
216
+ if (tone === "violet") {
217
+ return "#7c3aed";
218
+ }
219
+ if (tone === "gray") {
220
+ return "#94a3b8";
221
+ }
222
+ return "#2563eb";
223
+ }
224
+ function compactHandlerName(label) {
225
+ if (!label) {
226
+ return "Unknown";
227
+ }
228
+ if (label.includes("#")) {
229
+ const [className, methodName] = label.split("#");
230
+ return `${compactClassName(className ?? "")}#${methodName ?? ""}`;
231
+ }
232
+ return compactClassName(label);
233
+ }
234
+ function compactHandlerClassName(label) {
235
+ if (!label) {
236
+ return "Unknown";
237
+ }
238
+ if (label.includes("#")) {
239
+ const [className] = label.split("#");
240
+ return compactClassName(className ?? "");
241
+ }
242
+ return compactClassName(label);
243
+ }
244
+ function compactClassName(label) {
245
+ const parts = String(label).split(".");
246
+ return parts[parts.length - 1] || label;
247
+ }
248
+ function getStatusShortLabel(status) {
249
+ const numeric = Number(status);
250
+ if (numeric === 401) {
251
+ return "Auth required";
252
+ }
253
+ if (numeric === 403) {
254
+ return "Permission denied";
255
+ }
256
+ if (numeric >= 500) {
257
+ return "Server error";
258
+ }
259
+ if (numeric >= 400) {
260
+ return "Client error";
261
+ }
262
+ if (numeric >= 200 && numeric < 300) {
263
+ return "OK";
264
+ }
265
+ return "Observed";
266
+ }
267
+ function getStatusLabel(status) {
268
+ const numeric = Number(status);
269
+ if (numeric === 401) {
270
+ return "login required · 401";
271
+ }
272
+ if (numeric === 403) {
273
+ return "permission denied · 403";
274
+ }
275
+ if (numeric >= 500) {
276
+ return `server error · ${status}`;
277
+ }
278
+ if (numeric >= 400) {
279
+ return `client error · ${status}`;
280
+ }
281
+ if (numeric >= 200 && numeric < 300) {
282
+ return `success · ${status}`;
283
+ }
284
+ return `status ${status}`;
285
+ }
@@ -0,0 +1,5 @@
1
+ import type { OverlayApiEvent } from "./types.js";
2
+ export declare function RecentApiEventsTable({ events, selectedEventId }: {
3
+ events: OverlayApiEvent[];
4
+ selectedEventId?: string | null;
5
+ }): JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from "react";
3
+ import { Badge, Table } from "./ui.js";
4
+ export function RecentApiEventsTable({ events, selectedEventId }) {
5
+ const [filter, setFilter] = useState("action");
6
+ const actionEvents = useMemo(() => events.filter((event) => Boolean(event.triggeredBy)), [events]);
7
+ const backgroundEvents = useMemo(() => events.filter((event) => !event.triggeredBy), [events]);
8
+ const visibleEvents = filter === "action" ? actionEvents : filter === "background" ? backgroundEvents : events;
9
+ const emptyText = filter === "action"
10
+ ? "No user-action API requests yet."
11
+ : filter === "background"
12
+ ? "No background API requests observed."
13
+ : "No API events observed yet.";
14
+ return (_jsxs("section", { className: "anlyx-events-table-section", children: [_jsxs("div", { className: "anlyx-events-table-head", children: [_jsxs("div", { children: [_jsx("h3", { children: "Recent API events" }), _jsx("p", { children: "Action requests are primary. Background traffic stays quiet until selected." })] }), _jsxs("div", { className: "anlyx-events-filter", role: "tablist", "aria-label": "Filter API events", children: [_jsxs("button", { "aria-selected": filter === "action", type: "button", onClick: () => setFilter("action"), children: ["Actions ", _jsx("span", { children: actionEvents.length })] }), _jsxs("button", { "aria-selected": filter === "background", type: "button", onClick: () => setFilter("background"), children: ["Background ", _jsx("span", { children: backgroundEvents.length })] }), _jsxs("button", { "aria-selected": filter === "all", type: "button", onClick: () => setFilter("all"), children: ["All ", _jsx("span", { children: events.length })] })] })] }), visibleEvents.length === 0 ? (_jsx("div", { className: "anlyx-ov-empty", children: emptyText })) : (_jsxs(Table, { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Method" }), _jsx("th", { children: "Path" }), _jsx("th", { children: "Status" }), _jsx("th", { children: "Match" }), _jsx("th", { children: "Source" }), _jsx("th", { children: "Duration" })] }) }), _jsx("tbody", { children: visibleEvents.slice(0, 6).map((event) => (_jsxs("tr", { className: event.id === selectedEventId ? "anlyx-events-table-row--selected" : "", "data-event-id": event.id, tabIndex: 0, title: "Inspect this API event", children: [_jsx("td", { children: _jsx(Badge, { tone: "blue", children: event.method }) }), _jsx("td", { className: "anlyx-events-table-path", children: event.path }), _jsx("td", { children: _jsx(Badge, { tone: Number(event.status) >= 400 ? "amber" : "green", children: event.status }) }), _jsx("td", { children: _jsx(Badge, { tone: event.matchedEndpoint ? "green" : "amber", children: event.matchedEndpoint ? "matched" : "unmatched" }) }), _jsx("td", { children: _jsx(Badge, { tone: event.triggeredBy ? "blue" : "gray", children: event.triggeredBy
15
+ ? "action"
16
+ : event.source === "health"
17
+ ? "health"
18
+ : "background" }) }), _jsxs("td", { children: [event.durationMs, "ms"] })] }, event.id))) })] }))] }));
19
+ }
@@ -0,0 +1,8 @@
1
+ import "@xyflow/react/dist/style.css";
2
+ import "./overlay.css";
3
+ import type { FlowDrawerProps } from "./types.js";
4
+ declare global {
5
+ interface Window {
6
+ __ANLYX_RENDER_FLOW_DRAWER__?: (container: Element, props: FlowDrawerProps) => void;
7
+ }
8
+ }
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createRoot } from "react-dom/client";
3
+ import "@xyflow/react/dist/style.css";
4
+ import "./overlay.css";
5
+ import { FlowDrawer } from "./FlowDrawer.js";
6
+ const roots = new WeakMap();
7
+ window.__ANLYX_RENDER_FLOW_DRAWER__ = (container, props) => {
8
+ const existingRoot = roots.get(container);
9
+ const root = existingRoot ?? createRoot(container);
10
+ if (!existingRoot) {
11
+ roots.set(container, root);
12
+ }
13
+ root.render(_jsx(FlowDrawer, { ...props }));
14
+ };
@@ -0,0 +1,2 @@
1
+ .react-flow{--xy-edge-stroke-default:#b1b1b7;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#555;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#ffffff80;--xy-minimap-background-color-default:#fff;--xy-minimap-mask-background-color-default:#f0f0f099;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#e2e2e2;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:transparent;--xy-background-pattern-dots-color-default:#91919a;--xy-background-pattern-lines-color-default:#eee;--xy-background-pattern-cross-color-default:#e2e2e2;background-color:var(--xy-background-color,var(--xy-background-color-default));--xy-node-color-default:inherit;--xy-node-border-default:1px solid #1a192b;--xy-node-background-color-default:#fff;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #00000014;--xy-node-boxshadow-selected-default:0 0 0 .5px #1a192b;--xy-node-border-radius-default:3px;--xy-handle-background-color-default:#1a192b;--xy-handle-border-color-default:#fff;--xy-selection-background-color-default:#0059dc14;--xy-selection-border-default:1px dotted #0059dccc;--xy-controls-button-background-color-default:#fefefe;--xy-controls-button-background-color-hover-default:#f4f4f4;--xy-controls-button-color-default:inherit;--xy-controls-button-color-hover-default:inherit;--xy-controls-button-border-color-default:#eee;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#fff;--xy-edge-label-color-default:inherit;--xy-resize-background-color-default:#3367d9;direction:ltr}.react-flow.dark{--xy-edge-stroke-default:#3e3e3e;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#727272;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#96969640;--xy-minimap-background-color-default:#141414;--xy-minimap-mask-background-color-default:#3c3c3c99;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#2b2b2b;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:#141414;--xy-background-pattern-dots-color-default:#777;--xy-background-pattern-lines-color-default:#777;--xy-background-pattern-cross-color-default:#777;--xy-node-color-default:#f8f8f8;--xy-node-border-default:1px solid #3c3c3c;--xy-node-background-color-default:#1e1e1e;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #ffffff14;--xy-node-boxshadow-selected-default:0 0 0 .5px #999;--xy-handle-background-color-default:#bebebe;--xy-handle-border-color-default:#1e1e1e;--xy-selection-background-color-default:#c8c8dc14;--xy-selection-border-default:1px dotted #c8c8dccc;--xy-controls-button-background-color-default:#2b2b2b;--xy-controls-button-background-color-hover-default:#3e3e3e;--xy-controls-button-color-default:#f8f8f8;--xy-controls-button-color-hover-default:#fff;--xy-controls-button-border-color-default:#5b5b5b;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#141414;--xy-edge-label-color-default:#f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props,var(--xy-background-color,var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width,var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke,var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width,var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{pointer-events:none;position:absolute;overflow:visible}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected,var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:.5s linear infinite dashdraw}svg.react-flow__connectionline{z-index:1001;position:absolute;overflow:visible}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{-webkit-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default;position:absolute}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:0 0;pointer-events:none}.react-flow__nodesselection-rect{pointer-events:all;cursor:grab;position:absolute}.react-flow__handle{pointer-events:none;background-color:var(--xy-handle-background-color,var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color,var(--xy-handle-border-color-default));border-radius:100%;width:6px;min-width:5px;height:6px;min-height:5px;position:absolute}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;bottom:0;left:50%;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{z-index:5;margin:15px;position:absolute}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px)translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px)translateY(-50%)}.react-flow__attribution{background:var(--xy-attribution-background-color,var(--xy-attribution-background-color-default));margin:0;padding:2px 3px;font-size:10px}.react-flow__attribution a{color:#999;text-decoration:none}@keyframes dashdraw{0%{stroke-dashoffset:10px}}.react-flow__edgelabel-renderer{pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__viewport-portal{-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__minimap{background:var(--xy-minimap-background-color-props,var(--xy-minimap-background-color,var(--xy-minimap-background-color-default)))}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var(--xy-minimap-mask-background-color-props,var(--xy-minimap-mask-background-color,var(--xy-minimap-mask-background-color-default)));stroke:var(--xy-minimap-mask-stroke-color-props,var(--xy-minimap-mask-stroke-color,var(--xy-minimap-mask-stroke-color-default)));stroke-width:var(--xy-minimap-mask-stroke-width-props,var(--xy-minimap-mask-stroke-width,var(--xy-minimap-mask-stroke-width-default)))}.react-flow__minimap-node{fill:var(--xy-minimap-node-background-color-props,var(--xy-minimap-node-background-color,var(--xy-minimap-node-background-color-default)));stroke:var(--xy-minimap-node-stroke-color-props,var(--xy-minimap-node-stroke-color,var(--xy-minimap-node-stroke-color-default)));stroke-width:var(--xy-minimap-node-stroke-width-props,var(--xy-minimap-node-stroke-width,var(--xy-minimap-node-stroke-width-default)))}.react-flow__background-pattern.dots{fill:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-dots-color-default)))}.react-flow__background-pattern.lines{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-lines-color-default)))}.react-flow__background-pattern.cross{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-cross-color-default)))}.react-flow__controls{box-shadow:var(--xy-controls-box-shadow,var(--xy-controls-box-shadow-default));flex-direction:column;display:flex}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{background:var(--xy-controls-button-background-color,var(--xy-controls-button-background-color-default));border:none;border-bottom:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)));width:26px;height:26px;color:var(--xy-controls-button-color-props,var(--xy-controls-button-color,var(--xy-controls-button-color-default)));cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;padding:4px;display:flex}.react-flow__controls-button svg{fill:currentColor;width:100%;max-width:12px;max-height:12px}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{border-radius:var(--xy-node-border-radius,var(--xy-node-border-radius-default));width:150px;color:var(--xy-node-color,var(--xy-node-color-default));text-align:center;border:var(--xy-node-border,var(--xy-node-border-default));background-color:var(--xy-node-background-color,var(--xy-node-background-color-default));padding:10px;font-size:12px}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover,var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected,var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color,var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color,var(--xy-selection-background-color-default));border:var(--xy-selection-border,var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var(--xy-controls-button-background-color-hover-props,var(--xy-controls-button-background-color-hover,var(--xy-controls-button-background-color-hover-default)));color:var(--xy-controls-button-color-hover-props,var(--xy-controls-button-color-hover,var(--xy-controls-button-color-hover-default)))}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)))}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{background-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border:1px solid #fff;border-radius:1px;width:5px;height:5px;translate:-50% -50%}.react-flow__resize-control.handle.left{top:50%;left:0}.react-flow__resize-control.handle.right{top:50%;left:100%}.react-flow__resize-control.handle.top{top:0;left:50%}.react-flow__resize-control.handle.bottom{top:100%;left:50%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border-style:solid;border-width:0}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;height:100%;top:0;transform:translate(-50%)}.react-flow__resize-control.line.left{border-left-width:1px;left:0}.react-flow__resize-control.line.right{border-right-width:1px;left:100%}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{width:100%;height:1px;left:0;transform:translateY(-50%)}.react-flow__resize-control.line.top{border-top-width:1px;top:0}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color,var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color,var(--xy-edge-label-color-default))}*{box-sizing:border-box}.anlyx-flow-drawer-body{color:#0f172a;gap:9px;padding:11px;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;display:grid}.anlyx-ov-card{background:#fffffff7;border:1px solid #0f172a14;border-radius:12px;overflow:hidden;box-shadow:0 1px 2px #0f172a0a}.anlyx-ov-section-title{color:#64748b;letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid #eef2f7;margin:0;padding:10px 12px;font-size:10px;font-weight:900}.anlyx-ov-empty{color:#667085;padding:18px 12px;font-size:13px;line-height:1.5}.anlyx-ov-badge{color:#475569;white-space:nowrap;background:#fff;border:1px solid #e2e8f0;border-radius:999px;justify-content:center;align-items:center;width:fit-content;padding:3px 7px;font-size:9px;font-weight:850;line-height:1;display:inline-flex}.anlyx-ov-badge--blue{color:#1d4ed8;background:#eff6ff;border-color:#bfdbfe}.anlyx-ov-badge--green{color:#087443;background:#ecfdf3;border-color:#bbf7d0}.anlyx-ov-badge--amber{color:#b45309;background:#fffbeb;border-color:#fed7aa}.anlyx-ov-badge--violet{color:#6d28d9;background:#f5f3ff;border-color:#ddd6fe}.anlyx-ov-badge--gray,.anlyx-ov-badge--neutral{color:#475569;background:#f8fafc;border-color:#e2e8f0}.anlyx-no-primary-card{background:linear-gradient(#fffbeb8a,#fffffffa),#fff;gap:10px;padding:12px;display:grid}.anlyx-no-primary-card h3{color:#101828;letter-spacing:0;margin:8px 0 3px;font-size:15px;font-weight:900}.anlyx-no-primary-card p{color:#667085;text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:12px;font-weight:700;overflow:hidden}.anlyx-no-primary-card__note{color:#7c2d12;background:#fff7ed9e;border:1px dashed #fed7aa;border-radius:10px;padding:9px 10px;font-size:12px;font-weight:750;line-height:1.45}.anlyx-scanned-hints{padding:11px 12px}.anlyx-scanned-hints__head{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:10px;margin-bottom:10px;display:grid}.anlyx-scanned-hints__head h3{color:#101828;letter-spacing:0;margin:0 0 3px;font-size:13px;font-weight:900}.anlyx-scanned-hints__head p{color:#667085;margin:0;font-size:11px;font-weight:750;line-height:1.35}.anlyx-scanned-hints__list{gap:7px;display:grid}.anlyx-scanned-hint{background:#fbfdff;border:1px solid #e4e9f2;border-radius:10px;gap:5px;padding:8px 9px;display:grid}.anlyx-scanned-hint>div{align-items:center;gap:7px;min-width:0;display:flex}.anlyx-scanned-hint strong{color:#101828;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:900;overflow:hidden}.anlyx-scanned-hint p{color:#667085;text-overflow:ellipsis;white-space:nowrap;margin:0;font-size:11px;font-weight:700;overflow:hidden}.anlyx-captured-request__top{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:10px;padding:10px 12px 8px;display:grid}.anlyx-captured-request h3{color:#64748b;letter-spacing:.05em;text-transform:uppercase;margin:0 0 5px;font-size:10px;font-weight:900}.anlyx-captured-request__path{align-items:center;gap:8px;min-width:0;display:flex}.anlyx-captured-request__path strong{color:#101828;text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:900;line-height:1.35;overflow:hidden}.anlyx-captured-request__summary{color:#64748b;background:linear-gradient(#f8fafcdb,#fffffff0);border:1px solid #e5edf8;border-radius:10px;flex-wrap:wrap;align-items:center;gap:6px;margin:0 12px 8px;padding:7px 9px;font-size:9px;font-weight:850;line-height:1.3;display:flex}.anlyx-captured-request__summary span{align-items:center;gap:3px;min-width:0;display:inline-flex}.anlyx-captured-request__summary span:not(:last-child):after{content:"";background:#cbd5e1;border-radius:999px;width:3px;height:3px;margin-left:3px}.anlyx-captured-request__summary b{color:#334155;text-overflow:ellipsis;white-space:nowrap;font-weight:950;overflow:hidden}.anlyx-captured-request__steps{grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;padding:7px 11px 10px;display:grid}.anlyx-captured-step{background:linear-gradient(#fff,#fbfdff);border:1px solid #e2e8f0e6;border-radius:10px;grid-template-columns:22px minmax(0,1fr);align-items:start;gap:8px;min-width:0;padding:7px;display:grid}.anlyx-captured-step__dot{background:#eff6ff;border-radius:8px;width:22px;height:22px;box-shadow:inset 0 0 0 7px #2563eb}.anlyx-captured-step--green .anlyx-captured-step__dot{background:#dcfae6;box-shadow:inset 0 0 0 7px #16a34a}.anlyx-captured-step--amber .anlyx-captured-step__dot{background:#fff7ed;box-shadow:inset 0 0 0 7px #f59e0b}.anlyx-captured-step p,.anlyx-captured-step strong,.anlyx-captured-step span{text-overflow:ellipsis;white-space:nowrap;margin:0;display:block;overflow:hidden}.anlyx-captured-step p{color:#667085;letter-spacing:.08em;text-transform:uppercase;font-size:9px;font-weight:900}.anlyx-captured-step strong{color:#101828;margin-top:2px;font-size:11px;font-weight:900}.anlyx-captured-step span{color:#667085;margin-top:2px;font-size:10px;font-weight:700}.anlyx-flow-rf-section,.anlyx-events-table-section{background:#fff;border:1px solid #0f172a14;border-radius:12px;overflow:hidden;box-shadow:0 1px 2px #0f172a0a}.anlyx-flow-rf-head,.anlyx-events-table-head{justify-content:space-between;align-items:center;gap:10px;padding:10px 12px;display:flex}.anlyx-flow-rf-head h3,.anlyx-events-table-head h3{color:#344054;margin:0;font-size:12px;font-weight:900}.anlyx-flow-rf-head p{color:#8390a4;margin:3px 0 0;font-size:9px;font-weight:750;line-height:1.35}.anlyx-flow-rf-head__badges{flex-wrap:wrap;flex-shrink:0;justify-content:flex-end;gap:5px;display:inline-flex}.anlyx-events-table-head p{color:#8390a4;margin:3px 0 0;font-size:9px;font-weight:750;line-height:1.35}.anlyx-events-filter{background:#f8fafc;border:1px solid #e2e8f0;border-radius:999px;flex-shrink:0;gap:2px;padding:2px;display:inline-flex}.anlyx-events-filter button{color:#64748b;cursor:pointer;white-space:nowrap;background:0 0;border:0;border-radius:999px;height:24px;padding:0 7px;font-size:9px;font-weight:850}.anlyx-events-filter button[aria-selected=true]{color:#1d4ed8;background:#fff;box-shadow:0 1px 2px #0f172a14}.anlyx-events-filter span{color:#94a3b8;margin-left:3px}.anlyx-flow-rf-canvas{cursor:grab;background:radial-gradient(circle at 50% 30%,#2563eb0d,#0000 34%),#fbfdff;border:1px solid #cbd5e1bd;border-radius:12px;height:318px;margin:0 12px;overflow:hidden}.anlyx-flow-rf-canvas:active{cursor:grabbing}.anlyx-flow-rf-canvas .react-flow__background{opacity:.46}.anlyx-flow-rf-canvas .react-flow__viewport{transition:transform .18s}.anlyx-flow-rf-controls{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffffe0;border:1px solid #cbd5e1cc;border-radius:9px;gap:4px;padding:3px;display:inline-flex;box-shadow:0 8px 18px #0f172a14}.anlyx-flow-rf-controls button{color:#475467;cursor:pointer;white-space:nowrap;background:0 0;border:0;border-radius:7px;height:24px;padding:0 8px;font-size:10px;font-weight:850}.anlyx-flow-rf-controls button:hover{color:#1d4ed8;background:#eff6ff}.anlyx-flow-rf-controls button:focus-visible{outline-offset:1px;outline:2px solid #2563eb61}.anlyx-flow-rf-node{background:linear-gradient(#fffffffc,#f8fafcf5);border-color:#93c5fdf2 #93c5fdf2 #93c5fdf2 #2563eb;border-left-style:solid;border-left-width:2px;grid-template-rows:auto minmax(0,1fr) auto;gap:6px;width:168px;height:116px;padding:10px 11px;display:grid;position:relative;box-shadow:0 1px #0f172a05,0 14px 30px #0f172a0f}.anlyx-flow-rf-node--violet{border-color:#ddd6fe #ddd6fe #ddd6fe #7c3aed}.anlyx-flow-rf-node--amber{background:linear-gradient(#fff,#fffaf0);border-color:#fed7aa #fed7aa #fed7aa #f59e0b}.anlyx-flow-rf-node--green{border-color:#bbf7d0 #bbf7d0 #bbf7d0 #0f766e}.anlyx-flow-rf-node--gray{border-color:#e2e8f0 #e2e8f0 #e2e8f0 #94a3b8}.anlyx-flow-rf-node--blocked{box-shadow:0 0 0 1px #f59e0b1a,0 14px 30px #f59e0b14}.anlyx-flow-rf-node--scanned{box-shadow:none;opacity:.82;background:linear-gradient(#ffffffe6,#f8fafcc7);border-style:dashed}.anlyx-flow-rf-node--scanned .anlyx-flow-rf-node__value,.anlyx-flow-rf-node--scanned .anlyx-flow-rf-node__sub{color:#64748b}.anlyx-flow-rf-node__top{align-items:center;gap:7px;min-width:0;display:flex}.anlyx-flow-rf-node__icon{color:#2563eb;background:#eff6ff;border-radius:8px;flex:none;place-items:center;width:22px;height:22px;display:inline-grid}.anlyx-flow-rf-node--violet .anlyx-flow-rf-node__icon{color:#7c3aed;background:#f5f3ff}.anlyx-flow-rf-node--amber .anlyx-flow-rf-node__icon{color:#d97706;background:#fff7ed}.anlyx-flow-rf-node--green .anlyx-flow-rf-node__icon{color:#0f766e;background:#ecfdf3}.anlyx-flow-rf-node__label{color:#475467;letter-spacing:.04em;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap;font-size:9px;font-weight:950;overflow:hidden}.anlyx-flow-rf-node__step{color:#98a2b3;text-align:right;min-width:18px;margin-left:auto;font-size:10px;font-weight:950}.anlyx-flow-rf-node__value{color:#101828;-webkit-line-clamp:2;-webkit-box-orient:vertical;margin:0;font-size:11px;font-weight:900;line-height:1.28;display:-webkit-box;overflow:hidden}.anlyx-flow-rf-node__sub{color:#667085;text-overflow:ellipsis;white-space:nowrap;margin:-3px 0 0;font-size:9px;font-weight:700;overflow:hidden}.anlyx-flow-rf-handle{background:#93c5fd;border:2px solid #fff;width:8px;height:8px;box-shadow:0 0 0 1px #2563eb47}.anlyx-flow-rf-edge{stroke-width:2px;fill:none}.anlyx-flow-rf-edge--blue{stroke:#2563eb}.anlyx-flow-rf-edge--violet{stroke:#7c3aed}.anlyx-flow-rf-edge--amber{stroke:#f59e0b}.anlyx-flow-rf-edge--gray{stroke:#94a3b8;stroke-width:1.5px;stroke-dasharray:5 5}.react-flow__edge.animated .anlyx-flow-rf-edge{stroke-dasharray:7 7;animation:.9s linear infinite anlyx-edge-dash}@keyframes anlyx-edge-dash{to{stroke-dashoffset:-14px}}.anlyx-flow-rf-note{color:#738196;background:#f8fafc61;border-top:1px solid #e2e8f08c;margin:0;padding:8px 12px;font-size:10px;font-weight:700;line-height:1.4}.anlyx-flow-rf-empty{color:#667085;padding:18px 12px;font-size:13px}.anlyx-ov-table{border-collapse:collapse;width:100%;font-size:9px}.anlyx-ov-table th{color:#667085;letter-spacing:.07em;text-align:left;text-transform:uppercase;padding:0 10px 6px;font-size:9px;font-weight:900}.anlyx-ov-table td{color:#64748b;border-top:1px solid #f1f5f9;height:28px;padding:4px 10px;font-weight:700}.anlyx-ov-table tbody tr[data-event-id]{cursor:pointer}.anlyx-ov-table tbody tr[data-event-id]:hover{background:#2563eb09}.anlyx-ov-table tbody tr.anlyx-events-table-row--selected{background:#2563eb0f;box-shadow:inset 2px 0 #2563eb}.anlyx-ov-table tbody tr[data-event-id]:focus-visible{outline-offset:-2px;outline:2px solid #2563eb57}.anlyx-events-table-path{color:#1f2937;text-overflow:ellipsis;white-space:nowrap;max-width:340px;overflow:hidden}.anlyx-unmatched-card{background:#fffaf5;border-color:#fed7aa;padding:14px}.anlyx-unmatched-card h3{color:#9a3412;margin:0;font-size:13px;font-weight:900}.anlyx-unmatched-card p{color:#667085;margin:5px 0 0;font-size:12px;font-weight:700;line-height:1.45}@media (width<=760px){.anlyx-flow-rf-head{flex-direction:column;align-items:flex-start}.anlyx-flow-rf-head__badges{justify-content:flex-start}.anlyx-events-table-head{flex-direction:column;align-items:flex-start}.anlyx-events-filter{max-width:100%;overflow-x:auto}.anlyx-captured-request__steps{grid-template-columns:1fr}.anlyx-flow-rf-canvas{height:320px}}
2
+ /*$vite$:1*/