@anlyx/ui 0.1.1 → 0.1.2

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 (100) hide show
  1. package/dist/components/AnlyxAppShell.d.ts +1 -1
  2. package/dist/components/AnlyxAppShell.js +150 -15
  3. package/dist/components/ApiCallList.d.ts +0 -1
  4. package/dist/components/ApiCallList.js +0 -1
  5. package/dist/components/CanvasPlaceholder.d.ts +0 -1
  6. package/dist/components/CanvasPlaceholder.js +0 -1
  7. package/dist/components/CaptureStatusEmptyState.d.ts +0 -1
  8. package/dist/components/CaptureStatusEmptyState.js +5 -4
  9. package/dist/components/EndpointList.d.ts +0 -1
  10. package/dist/components/EndpointList.js +1 -2
  11. package/dist/components/EndpointMapCanvas.d.ts +5 -2
  12. package/dist/components/EndpointMapCanvas.js +93 -13
  13. package/dist/components/FlowLegend.d.ts +4 -2
  14. package/dist/components/FlowLegend.js +4 -2
  15. package/dist/components/FlowNodeCard.d.ts +1 -2
  16. package/dist/components/FlowNodeCard.js +25 -3
  17. package/dist/components/InspectorPanel.d.ts +8 -3
  18. package/dist/components/InspectorPanel.js +17 -3
  19. package/dist/components/PageList.d.ts +0 -1
  20. package/dist/components/PageList.js +1 -2
  21. package/dist/components/PageStoryboardCard.d.ts +0 -1
  22. package/dist/components/PageStoryboardCard.js +4 -2
  23. package/dist/components/PageStoryboardView.d.ts +4 -3
  24. package/dist/components/PageStoryboardView.js +5 -3
  25. package/dist/components/ProcessFlowView.d.ts +21 -0
  26. package/dist/components/ProcessFlowView.js +9 -0
  27. package/dist/components/ProcessTimeline.d.ts +9 -0
  28. package/dist/components/ProcessTimeline.js +46 -0
  29. package/dist/components/ReplayControls.d.ts +5 -2
  30. package/dist/components/ReplayControls.js +4 -3
  31. package/dist/components/ScreenshotSegmentCard.d.ts +0 -1
  32. package/dist/components/ScreenshotSegmentCard.js +0 -1
  33. package/dist/components/Sidebar.d.ts +5 -4
  34. package/dist/components/Sidebar.js +6 -3
  35. package/dist/components/StatusBadge.d.ts +0 -1
  36. package/dist/components/StatusBadge.js +0 -1
  37. package/dist/flow/build-react-flow-model.d.ts +6 -2
  38. package/dist/flow/build-react-flow-model.js +15 -18
  39. package/dist/flow/layout/elk-layout.d.ts +7 -0
  40. package/dist/flow/layout/elk-layout.js +74 -0
  41. package/dist/index.d.ts +0 -1
  42. package/dist/index.js +0 -1
  43. package/dist/mock-data.d.ts +0 -1
  44. package/dist/mock-data.js +0 -1
  45. package/dist/replay/build-replay-steps.d.ts +0 -1
  46. package/dist/replay/build-replay-steps.js +0 -1
  47. package/dist/replay/use-replay-lite.d.ts +2 -2
  48. package/dist/replay/use-replay-lite.js +1 -1
  49. package/dist/styles.css +826 -100
  50. package/dist/viewer/ViewerApp.d.ts +0 -1
  51. package/dist/viewer/ViewerApp.js +0 -1
  52. package/dist/viewer/viewer-entry.d.ts +1 -1
  53. package/dist/viewer/viewer-entry.js +1 -1
  54. package/package.json +6 -2
  55. package/dist/components/AnlyxAppShell.d.ts.map +0 -1
  56. package/dist/components/AnlyxAppShell.js.map +0 -1
  57. package/dist/components/ApiCallList.d.ts.map +0 -1
  58. package/dist/components/ApiCallList.js.map +0 -1
  59. package/dist/components/CanvasPlaceholder.d.ts.map +0 -1
  60. package/dist/components/CanvasPlaceholder.js.map +0 -1
  61. package/dist/components/CaptureStatusEmptyState.d.ts.map +0 -1
  62. package/dist/components/CaptureStatusEmptyState.js.map +0 -1
  63. package/dist/components/EndpointList.d.ts.map +0 -1
  64. package/dist/components/EndpointList.js.map +0 -1
  65. package/dist/components/EndpointMapCanvas.d.ts.map +0 -1
  66. package/dist/components/EndpointMapCanvas.js.map +0 -1
  67. package/dist/components/FlowLegend.d.ts.map +0 -1
  68. package/dist/components/FlowLegend.js.map +0 -1
  69. package/dist/components/FlowNodeCard.d.ts.map +0 -1
  70. package/dist/components/FlowNodeCard.js.map +0 -1
  71. package/dist/components/InspectorPanel.d.ts.map +0 -1
  72. package/dist/components/InspectorPanel.js.map +0 -1
  73. package/dist/components/PageList.d.ts.map +0 -1
  74. package/dist/components/PageList.js.map +0 -1
  75. package/dist/components/PageStoryboardCard.d.ts.map +0 -1
  76. package/dist/components/PageStoryboardCard.js.map +0 -1
  77. package/dist/components/PageStoryboardView.d.ts.map +0 -1
  78. package/dist/components/PageStoryboardView.js.map +0 -1
  79. package/dist/components/ReplayControls.d.ts.map +0 -1
  80. package/dist/components/ReplayControls.js.map +0 -1
  81. package/dist/components/ScreenshotSegmentCard.d.ts.map +0 -1
  82. package/dist/components/ScreenshotSegmentCard.js.map +0 -1
  83. package/dist/components/Sidebar.d.ts.map +0 -1
  84. package/dist/components/Sidebar.js.map +0 -1
  85. package/dist/components/StatusBadge.d.ts.map +0 -1
  86. package/dist/components/StatusBadge.js.map +0 -1
  87. package/dist/flow/build-react-flow-model.d.ts.map +0 -1
  88. package/dist/flow/build-react-flow-model.js.map +0 -1
  89. package/dist/index.d.ts.map +0 -1
  90. package/dist/index.js.map +0 -1
  91. package/dist/mock-data.d.ts.map +0 -1
  92. package/dist/mock-data.js.map +0 -1
  93. package/dist/replay/build-replay-steps.d.ts.map +0 -1
  94. package/dist/replay/build-replay-steps.js.map +0 -1
  95. package/dist/replay/use-replay-lite.d.ts.map +0 -1
  96. package/dist/replay/use-replay-lite.js.map +0 -1
  97. package/dist/viewer/ViewerApp.d.ts.map +0 -1
  98. package/dist/viewer/ViewerApp.js.map +0 -1
  99. package/dist/viewer/viewer-entry.d.ts.map +0 -1
  100. package/dist/viewer/viewer-entry.js.map +0 -1
@@ -2,5 +2,5 @@ import type { ScanResult } from "@anlyx/core";
2
2
  export type AnlyxAppShellProps = {
3
3
  data: ScanResult;
4
4
  };
5
+ export type ViewMode = "structure" | "frontend" | "process";
5
6
  export declare function AnlyxAppShell({ data }: AnlyxAppShellProps): JSX.Element;
6
- //# sourceMappingURL=AnlyxAppShell.d.ts.map
@@ -1,39 +1,93 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
+ import { Group, Panel, Separator, usePanelRef } from "react-resizable-panels";
3
4
  import { EndpointMapCanvas } from "./EndpointMapCanvas.js";
4
5
  import { InspectorPanel } from "./InspectorPanel.js";
5
6
  import { PageStoryboardView } from "./PageStoryboardView.js";
6
- import { ReplayControls } from "./ReplayControls.js";
7
+ import { ProcessFlowView } from "./ProcessFlowView.js";
7
8
  import { Sidebar } from "./Sidebar.js";
8
9
  import { useReplayLite } from "../replay/use-replay-lite.js";
10
+ const STORAGE_KEYS = {
11
+ leftCollapsed: "anlyx:ui:leftCollapsed",
12
+ panelLayout: "anlyx:ui:panelLayout",
13
+ rightCollapsed: "anlyx:ui:rightCollapsed",
14
+ selectedEndpointId: "anlyx:ui:selectedEndpointId",
15
+ selectedPageId: "anlyx:ui:selectedPageId"
16
+ };
17
+ const DEFAULT_PANEL_LAYOUT = {
18
+ left: 22,
19
+ center: 52,
20
+ right: 26
21
+ };
9
22
  export function AnlyxAppShell({ data }) {
10
- const [activeView, setActiveView] = useState("endpoint");
11
- const [selectedEndpointId, setSelectedEndpointId] = useState(data.endpoints[0]?.id);
12
- const [selectedPageId, setSelectedPageId] = useState(data.pages[0]?.id);
23
+ const [activeView, setActiveView] = useState("structure");
24
+ const [selectedEndpointId, setSelectedEndpointId] = usePersistentString(STORAGE_KEYS.selectedEndpointId, selectInitialEndpointId(data));
25
+ const [selectedPageId, setSelectedPageId] = usePersistentString(STORAGE_KEYS.selectedPageId, data.pages[0]?.id);
26
+ const leftPanelRef = usePanelRef();
27
+ const rightPanelRef = usePanelRef();
28
+ const [leftCollapsed, setLeftCollapsed] = usePersistentBoolean(STORAGE_KEYS.leftCollapsed, false);
29
+ const [rightCollapsed, setRightCollapsed] = usePersistentBoolean(STORAGE_KEYS.rightCollapsed, false);
30
+ const panelLayout = useMemo(() => readPanelLayout(), []);
31
+ const [replaySpeed, setReplaySpeed] = useState(800);
13
32
  const selectedEndpoint = data.endpoints.find((endpoint) => endpoint.id === selectedEndpointId) ?? data.endpoints[0];
14
33
  const selectedPage = data.pages.find((page) => page.id === selectedPageId) ?? data.pages[0];
15
34
  const selectedFlow = useMemo(() => data.flows.find((flow) => flow.endpointId === selectedEndpoint?.id), [data.flows, selectedEndpoint?.id]);
16
35
  const replayMainPath = useMemo(() => getReplayMainPath(selectedFlow, selectedEndpoint?.id), [selectedEndpoint?.id, selectedFlow]);
17
- const replay = useReplayLite({ mainPath: replayMainPath });
36
+ const replay = useReplayLite({ intervalMs: replaySpeed, mainPath: replayMainPath });
18
37
  const replayUnavailable = replayMainPath.length === 0;
19
38
  const [selectedNodeId, setSelectedNodeId] = useState(() => findDefaultNode(selectedFlow)?.id);
20
39
  const selectedNode = findFlowNode(selectedFlow, selectedNodeId) ?? findDefaultNode(selectedFlow);
21
40
  useEffect(() => {
22
- setSelectedEndpointId((current) => data.endpoints.some((endpoint) => endpoint.id === current) ? current : data.endpoints[0]?.id);
23
- }, [data.endpoints]);
41
+ setSelectedEndpointId((current) => data.endpoints.some((endpoint) => endpoint.id === current)
42
+ ? current
43
+ : selectInitialEndpointId(data));
44
+ }, [data, setSelectedEndpointId]);
24
45
  useEffect(() => {
25
46
  setSelectedPageId((current) => data.pages.some((page) => page.id === current) ? current : data.pages[0]?.id);
26
47
  }, [data.pages]);
27
48
  useEffect(() => {
28
49
  setSelectedNodeId(findDefaultNode(selectedFlow)?.id);
29
50
  }, [selectedFlow]);
30
- return (_jsxs("div", { className: "anlyx-shell", role: "application", "aria-label": "Anlyx application shell", children: [_jsx(Sidebar, { data: data, activeView: activeView, selectedEndpointId: selectedEndpoint?.id, selectedPageId: selectedPage?.id, onSelectView: setActiveView, onSelectEndpoint: (endpoint) => {
31
- setSelectedEndpointId(endpoint.id);
32
- setActiveView("endpoint");
33
- }, onSelectPage: (page) => {
34
- setSelectedPageId(page.id);
35
- setActiveView("pages");
36
- } }), _jsxs("div", { className: "anlyx-main", children: [activeView === "endpoint" ? (_jsx(EndpointMapCanvas, { endpoint: selectedEndpoint, flow: selectedFlow, replayState: replay.state, selectedNodeId: selectedNode?.id, onSelectNode: (node) => setSelectedNodeId(node.id) })) : null, activeView === "pages" ? _jsx(PageStoryboardView, { page: selectedPage }) : null, activeView === "replay" ? (_jsx(EndpointMapCanvas, { endpoint: selectedEndpoint, flow: selectedFlow, replayState: replay.state, selectedNodeId: selectedNode?.id, onSelectNode: (node) => setSelectedNodeId(node.id) })) : null, _jsx(ReplayControls, { disabled: replayUnavailable, loop: replay.loop, state: replay.state, unavailableReason: "Replay is unavailable because this endpoint has no main flow.", onPause: replay.pause, onPlay: replay.play, onRestart: replay.restart, onToggleLoop: replay.toggleLoop })] }), _jsx(InspectorPanel, { data: data, selectedFlow: selectedFlow, selectedNode: selectedNode }), _jsxs("div", { className: "anlyx-generated-at", children: ["Generated ", data.generatedAt] })] }));
51
+ useEffect(() => {
52
+ if (leftCollapsed) {
53
+ leftPanelRef.current?.collapse();
54
+ }
55
+ }, [leftCollapsed, leftPanelRef]);
56
+ useEffect(() => {
57
+ if (rightCollapsed) {
58
+ rightPanelRef.current?.collapse();
59
+ }
60
+ }, [rightCollapsed, rightPanelRef]);
61
+ const toggleLeftPanel = () => {
62
+ if (leftCollapsed) {
63
+ leftPanelRef.current?.expand();
64
+ setLeftCollapsed(false);
65
+ }
66
+ else {
67
+ leftPanelRef.current?.collapse();
68
+ setLeftCollapsed(true);
69
+ }
70
+ };
71
+ const toggleRightPanel = () => {
72
+ if (rightCollapsed) {
73
+ rightPanelRef.current?.expand();
74
+ setRightCollapsed(false);
75
+ }
76
+ else {
77
+ rightPanelRef.current?.collapse();
78
+ setRightCollapsed(true);
79
+ }
80
+ };
81
+ return (_jsxs("div", { className: "anlyx-shell", role: "application", "aria-label": "Anlyx application shell", children: [_jsxs(Group, { className: "anlyx-panel-group", defaultLayout: panelLayout, id: "anlyx-main-panels", orientation: "horizontal", onLayoutChanged: (layout) => writeLocalStorage(STORAGE_KEYS.panelLayout, JSON.stringify(layout)), children: [_jsx(Panel, { className: "anlyx-panel anlyx-panel--sidebar", collapsedSize: "52px", collapsible: true, defaultSize: "300px", id: "left", maxSize: "420px", minSize: "240px", panelRef: leftPanelRef, children: _jsx(Sidebar, { data: data, activeView: activeView, collapsed: leftCollapsed, selectedEndpointId: selectedEndpoint?.id, selectedPageId: selectedPage?.id, onSelectView: setActiveView, onToggleCollapsed: toggleLeftPanel, onSelectEndpoint: (endpoint) => {
82
+ setSelectedEndpointId(endpoint.id);
83
+ setActiveView("structure");
84
+ }, onSelectPage: (page) => {
85
+ setSelectedPageId(page.id);
86
+ setActiveView("frontend");
87
+ } }) }), _jsx(Separator, { "aria-label": "Resize navigation panel", className: "anlyx-resize-handle", children: _jsx("span", { "aria-hidden": "true" }) }), _jsx(Panel, { className: "anlyx-panel anlyx-panel--main", id: "center", minSize: "420px", children: _jsxs("div", { className: activeView === "process" ? "anlyx-main anlyx-main--process" : "anlyx-main", "aria-live": "polite", children: [activeView === "structure" ? (_jsx(EndpointMapCanvas, { eyebrow: "Backend API Structure", endpoint: selectedEndpoint, flow: selectedFlow, replayState: replay.state, selectedNodeId: selectedNode?.id, toolbar: _jsx(StructureToolbar, {}), onSelectNode: (node) => setSelectedNodeId(node.id) })) : null, activeView === "frontend" ? (_jsx(PageStoryboardView, { data: data, page: selectedPage, onViewProcessFlow: setActiveView })) : null, activeView === "process" ? (_jsx(ProcessFlowView, { endpoint: selectedEndpoint, flow: selectedFlow, replayDisabled: replayUnavailable, replayLoop: replay.loop, replaySpeed: replaySpeed, replayState: replay.state, replaySteps: replay.steps, selectedNodeId: selectedNode?.id, onPause: replay.pause, onPlay: replay.play, onRestart: replay.restart, onSelectNode: (node) => setSelectedNodeId(node.id), onSpeedChange: setReplaySpeed, onToggleLoop: replay.toggleLoop, onViewStructure: () => setActiveView("structure") })) : null] }) }), _jsx(Separator, { "aria-label": "Resize inspector panel", className: "anlyx-resize-handle", children: _jsx("span", { "aria-hidden": "true" }) }), _jsx(Panel, { className: "anlyx-panel anlyx-panel--inspector", collapsedSize: "52px", collapsible: true, defaultSize: "360px", id: "right", maxSize: "520px", minSize: "300px", panelRef: rightPanelRef, children: _jsx(InspectorPanel, { activeView: activeView, collapsed: rightCollapsed, data: data, replayState: replay.state, selectedFlow: selectedFlow, selectedNode: selectedNode, selectedPage: selectedPage, onToggleCollapsed: toggleRightPanel }) })] }), _jsxs("div", { className: "anlyx-generated-at", children: ["Generated ", data.generatedAt] })] }));
88
+ }
89
+ function StructureToolbar() {
90
+ return (_jsxs("div", { className: "anlyx-toolbar", "aria-label": "Structure view actions", children: [_jsx("button", { className: "anlyx-toolbar-button", type: "button", children: "Fit view" }), _jsxs("select", { "aria-label": "Zoom level", className: "anlyx-toolbar-select", defaultValue: "100", children: [_jsx("option", { value: "75", children: "75%" }), _jsx("option", { value: "100", children: "100%" }), _jsx("option", { value: "125", children: "125%" })] }), _jsx("button", { className: "anlyx-toolbar-button anlyx-toolbar-button--icon", type: "button", children: "More" })] }));
37
91
  }
38
92
  function getReplayMainPath(flow, endpointId) {
39
93
  if (!flow || flow.mainPath.length === 0) {
@@ -45,6 +99,20 @@ function getReplayMainPath(flow, endpointId) {
45
99
  }
46
100
  return flow.mainPath;
47
101
  }
102
+ function selectInitialEndpointId(data) {
103
+ const flowByEndpointId = new Map(data.flows.map((flow) => [flow.endpointId, flow]));
104
+ const endpointsByScore = [...data.endpoints].sort((left, right) => scoreEndpoint(right, flowByEndpointId) - scoreEndpoint(left, flowByEndpointId));
105
+ return endpointsByScore[0]?.id;
106
+ }
107
+ function scoreEndpoint(endpoint, flowByEndpointId) {
108
+ const flow = flowByEndpointId.get(endpoint.id);
109
+ const hasDatabase = flow?.nodes.some((node) => node.type === "database") ? 1 : 0;
110
+ const confidenceScore = endpoint.confidence === "high" ? 4 : endpoint.confidence === "medium" ? 2 : 0;
111
+ return ((flow?.mainPath.length ?? 0) * 8 +
112
+ (flow?.subFlows.length ?? 0) * 5 +
113
+ hasDatabase * 6 +
114
+ confidenceScore);
115
+ }
48
116
  function findDefaultNode(flow) {
49
117
  if (!flow) {
50
118
  return undefined;
@@ -58,4 +126,71 @@ function findFlowNode(flow, nodeId) {
58
126
  return (flow.nodes.find((node) => node.id === nodeId) ??
59
127
  flow.subFlows.flatMap((subFlow) => subFlow.nodes).find((node) => node.id === nodeId));
60
128
  }
61
- //# sourceMappingURL=AnlyxAppShell.js.map
129
+ function usePersistentBoolean(key, defaultValue) {
130
+ const [value, setValue] = useState(() => {
131
+ const storedValue = readLocalStorage(key);
132
+ if (storedValue === "true") {
133
+ return true;
134
+ }
135
+ if (storedValue === "false") {
136
+ return false;
137
+ }
138
+ return defaultValue;
139
+ });
140
+ useEffect(() => {
141
+ writeLocalStorage(key, String(value));
142
+ }, [key, value]);
143
+ return [value, setValue];
144
+ }
145
+ function usePersistentString(key, fallback) {
146
+ const [value, setValue] = useState(() => {
147
+ if (typeof window === "undefined") {
148
+ return fallback;
149
+ }
150
+ return window.localStorage.getItem(key) ?? fallback;
151
+ });
152
+ useEffect(() => {
153
+ if (typeof window === "undefined") {
154
+ return;
155
+ }
156
+ if (value) {
157
+ window.localStorage.setItem(key, value);
158
+ }
159
+ else {
160
+ window.localStorage.removeItem(key);
161
+ }
162
+ }, [key, value]);
163
+ return [value, setValue];
164
+ }
165
+ function readLocalStorage(key) {
166
+ if (typeof window === "undefined") {
167
+ return null;
168
+ }
169
+ return window.localStorage.getItem(key);
170
+ }
171
+ function writeLocalStorage(key, value) {
172
+ if (typeof window === "undefined") {
173
+ return;
174
+ }
175
+ window.localStorage.setItem(key, value);
176
+ }
177
+ function readPanelLayout() {
178
+ const storedValue = readLocalStorage(STORAGE_KEYS.panelLayout);
179
+ if (!storedValue) {
180
+ return DEFAULT_PANEL_LAYOUT;
181
+ }
182
+ try {
183
+ const parsedValue = JSON.parse(storedValue);
184
+ return {
185
+ left: sanitizePanelSize(parsedValue.left, DEFAULT_PANEL_LAYOUT.left),
186
+ center: sanitizePanelSize(parsedValue.center, DEFAULT_PANEL_LAYOUT.center),
187
+ right: sanitizePanelSize(parsedValue.right, DEFAULT_PANEL_LAYOUT.right)
188
+ };
189
+ }
190
+ catch {
191
+ return DEFAULT_PANEL_LAYOUT;
192
+ }
193
+ }
194
+ function sanitizePanelSize(value, fallback) {
195
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
196
+ }
@@ -3,4 +3,3 @@ export type ApiCallListProps = {
3
3
  apiCalls: ApiCall[];
4
4
  };
5
5
  export declare function ApiCallList({ apiCalls }: ApiCallListProps): JSX.Element;
6
- //# sourceMappingURL=ApiCallList.d.ts.map
@@ -15,4 +15,3 @@ function statusTone(status) {
15
15
  }
16
16
  return "pending";
17
17
  }
18
- //# sourceMappingURL=ApiCallList.js.map
@@ -4,4 +4,3 @@ type CanvasPlaceholderProps = {
4
4
  };
5
5
  export declare function CanvasPlaceholder({ endpoint }: CanvasPlaceholderProps): JSX.Element;
6
6
  export {};
7
- //# sourceMappingURL=CanvasPlaceholder.d.ts.map
@@ -3,4 +3,3 @@ import { StatusBadge } from "./StatusBadge.js";
3
3
  export function CanvasPlaceholder({ endpoint }) {
4
4
  return (_jsxs("main", { className: "anlyx-workspace", "aria-label": "Workspace", children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Selected endpoint" }), _jsx("h1", { children: endpoint ? `${endpoint.method} ${endpoint.path}` : "No endpoint selected" })] }), endpoint ? (_jsx(StatusBadge, { tone: endpoint.confidence ?? "unknown", children: endpoint.confidence ?? "unknown" })) : null] }), _jsx("section", { className: "anlyx-canvas-placeholder", "aria-label": "Endpoint map canvas placeholder", children: _jsxs("div", { className: "anlyx-canvas-placeholder__content", children: [_jsx("p", { className: "anlyx-eyebrow", children: "Canvas" }), _jsx("h2", { children: "Endpoint Map will render here" })] }) })] }));
5
5
  }
6
- //# sourceMappingURL=CanvasPlaceholder.js.map
@@ -4,4 +4,3 @@ export type CaptureStatusEmptyStateProps = {
4
4
  reason?: string;
5
5
  };
6
6
  export declare function CaptureStatusEmptyState({ status, reason }: CaptureStatusEmptyStateProps): JSX.Element | null;
7
- //# sourceMappingURL=CaptureStatusEmptyState.d.ts.map
@@ -3,8 +3,9 @@ export function CaptureStatusEmptyState({ status, reason }) {
3
3
  if (status === "success") {
4
4
  return null;
5
5
  }
6
- const title = status === "failed" ? "Capture failed" : "Capture pending";
7
- const fallbackReason = status === "pending" ? "Waiting for capture input." : "Unknown";
8
- return (_jsxs("section", { className: `anlyx-capture-state anlyx-capture-state--${status}`, children: [_jsx("h2", { children: title }), _jsxs("p", { children: ["Reason: ", reason ?? fallbackReason] })] }));
6
+ const title = status === "failed" ? "Capture failed" : "Capture was skipped.";
7
+ const fallbackReason = status === "pending"
8
+ ? "Run `anlyx scan` without `--skip-capture` while your frontend server is running."
9
+ : "Unknown";
10
+ return (_jsxs("section", { className: `anlyx-capture-state anlyx-capture-state--${status}`, children: [_jsx("h2", { children: title }), _jsxs("p", { children: ["Reason: ", reason ?? fallbackReason] }), status === "pending" ? (_jsx("p", { children: "Dynamic routes may require `sampleParams` in `anlyx.config.ts`." })) : null] }));
9
11
  }
10
- //# sourceMappingURL=CaptureStatusEmptyState.js.map
@@ -6,4 +6,3 @@ type EndpointListProps = {
6
6
  };
7
7
  export declare function EndpointList({ endpoints, selectedEndpointId, onSelectEndpoint }: EndpointListProps): JSX.Element;
8
8
  export {};
9
- //# sourceMappingURL=EndpointList.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { StatusBadge } from "./StatusBadge.js";
3
3
  export function EndpointList({ endpoints, selectedEndpointId, onSelectEndpoint }) {
4
- return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-endpoints-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-endpoints-heading", children: "Endpoints" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Endpoint list", children: endpoints.map((endpoint) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-endpoint-button", type: "button", "aria-label": `${endpoint.method} ${endpoint.path} ${formatEndpointHandler(endpoint)}`, "aria-current": endpoint.id === selectedEndpointId ? "true" : undefined, onClick: () => onSelectEndpoint(endpoint), children: [_jsxs("span", { className: "anlyx-list-item__line", children: [_jsx(StatusBadge, { tone: endpoint.method, children: endpoint.method }), _jsx("span", { className: "anlyx-list-item__primary", children: endpoint.path })] }), _jsx("span", { className: "anlyx-list-item__meta", children: formatEndpointHandler(endpoint) })] }) }, endpoint.id))) })] }));
4
+ return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-endpoints-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-endpoints-heading", children: "Backend Endpoints" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Endpoint list", children: endpoints.map((endpoint) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-endpoint-button", type: "button", "aria-label": `${endpoint.method} ${endpoint.path} ${formatEndpointHandler(endpoint)}`, "aria-current": endpoint.id === selectedEndpointId ? "true" : undefined, onClick: () => onSelectEndpoint(endpoint), children: [_jsxs("span", { className: "anlyx-list-item__line", children: [_jsx(StatusBadge, { tone: endpoint.method, children: endpoint.method }), _jsx("span", { className: "anlyx-list-item__primary", children: endpoint.path })] }), _jsx("span", { className: "anlyx-list-item__meta", children: formatEndpointHandler(endpoint) })] }) }, endpoint.id))) })] }));
5
5
  }
6
6
  function formatEndpointHandler(endpoint) {
7
7
  if (endpoint.controller && endpoint.handler) {
@@ -9,4 +9,3 @@ function formatEndpointHandler(endpoint) {
9
9
  }
10
10
  return endpoint.handler ?? endpoint.controller ?? endpoint.id;
11
11
  }
12
- //# sourceMappingURL=EndpointList.js.map
@@ -4,8 +4,11 @@ export type EndpointMapCanvasProps = {
4
4
  endpoint: Endpoint | undefined;
5
5
  flow: EndpointFlow | undefined;
6
6
  selectedNodeId: string | undefined;
7
+ title?: string;
8
+ eyebrow?: string;
9
+ variant?: "structure" | "process";
7
10
  replayState?: ReplayLiteState;
11
+ toolbar?: JSX.Element;
8
12
  onSelectNode: (node: FlowNode) => void;
9
13
  };
10
- export declare function EndpointMapCanvas({ endpoint, flow, selectedNodeId, replayState, onSelectNode }: EndpointMapCanvasProps): JSX.Element;
11
- //# sourceMappingURL=EndpointMapCanvas.d.ts.map
14
+ export declare function EndpointMapCanvas({ endpoint, flow, selectedNodeId, title, eyebrow, variant, replayState, toolbar, onSelectNode }: EndpointMapCanvasProps): JSX.Element;
@@ -1,15 +1,42 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Background, BackgroundVariant, Controls, ReactFlow } from "@xyflow/react";
3
- import { useMemo } from "react";
2
+ import { Background, BackgroundVariant, BaseEdge, Controls, ReactFlow, getSmoothStepPath } from "@xyflow/react";
3
+ import { useEffect, useMemo, useState } from "react";
4
4
  import { FlowLegend } from "./FlowLegend.js";
5
5
  import { FlowNodeCard } from "./FlowNodeCard.js";
6
6
  import { StatusBadge } from "./StatusBadge.js";
7
7
  import { buildReactFlowModel } from "../flow/build-react-flow-model.js";
8
+ import { layoutWithElk } from "../flow/layout/elk-layout.js";
8
9
  const nodeTypes = {
9
10
  anlyxNode: FlowNodeCard
10
11
  };
11
- export function EndpointMapCanvas({ endpoint, flow, selectedNodeId, replayState, onSelectNode }) {
12
- const model = useMemo(() => (flow ? buildReactFlowModel(flow) : undefined), [flow]);
12
+ const edgeTypes = {
13
+ anlyxEdge: FlowEdgeLine
14
+ };
15
+ export function EndpointMapCanvas({ endpoint, flow, selectedNodeId, title, eyebrow = "Backend Endpoint Map", variant = "structure", replayState, toolbar, onSelectNode }) {
16
+ const fallbackModel = useMemo(() => (flow ? buildReactFlowModel(flow, { variant }) : undefined), [flow, variant]);
17
+ const [layoutedModel, setLayoutedModel] = useState(fallbackModel);
18
+ const model = layoutedModel ?? fallbackModel;
19
+ useEffect(() => {
20
+ let cancelled = false;
21
+ setLayoutedModel(fallbackModel);
22
+ if (!fallbackModel || isUnitTestRuntime()) {
23
+ return;
24
+ }
25
+ void layoutWithElk(fallbackModel, { variant })
26
+ .then((nextModel) => {
27
+ if (!cancelled) {
28
+ setLayoutedModel(isUsableLayout(nextModel, variant) ? nextModel : fallbackModel);
29
+ }
30
+ })
31
+ .catch(() => {
32
+ if (!cancelled) {
33
+ setLayoutedModel(fallbackModel);
34
+ }
35
+ });
36
+ return () => {
37
+ cancelled = true;
38
+ };
39
+ }, [fallbackModel, variant]);
13
40
  const nodes = useMemo(() => model?.nodes.map((node) => ({
14
41
  ...node,
15
42
  selected: node.id === selectedNodeId,
@@ -21,21 +48,75 @@ export function EndpointMapCanvas({ endpoint, flow, selectedNodeId, replayState,
21
48
  })) ?? [], [model, onSelectNode, replayState?.activeNodeId, selectedNodeId]);
22
49
  const edges = useMemo(() => model?.edges.map((edge) => {
23
50
  const isReplayActive = isReplayEdgeActive(edge, replayState?.activeEdge);
51
+ const replayClass = isReplayActive && replayState?.phase === "response"
52
+ ? "anlyx-flow-edge--replay-response"
53
+ : isReplayActive
54
+ ? "anlyx-flow-edge--replay-request"
55
+ : "";
24
56
  const edgeData = edge.data;
57
+ const nextData = {
58
+ edge: edgeData.edge,
59
+ flowRole: edgeData.flowRole,
60
+ ...(edgeData.confidence ? { confidence: edgeData.confidence } : {}),
61
+ isReplayActive,
62
+ ...(replayState?.phase ? { replayPhase: replayState.phase } : {})
63
+ };
25
64
  return {
26
65
  ...edge,
27
- className: [edge.className, isReplayActive ? "anlyx-flow-edge--replay-active" : ""]
66
+ className: [
67
+ edge.className,
68
+ isReplayActive ? "anlyx-flow-edge--replay-active" : "",
69
+ replayClass
70
+ ]
28
71
  .filter(Boolean)
29
72
  .join(" "),
30
- data: {
31
- edge: edgeData.edge,
32
- flowRole: edgeData.flowRole,
33
- ...(edgeData.confidence ? { confidence: edgeData.confidence } : {}),
34
- isReplayActive
35
- }
73
+ data: nextData
36
74
  };
37
75
  }) ?? [], [model, replayState?.activeEdge]);
38
- return (_jsxs("main", { className: "anlyx-workspace", children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Endpoint Map" }), _jsx("h1", { children: endpoint ? `${endpoint.method} ${endpoint.path}` : "No endpoint selected" })] }), endpoint ? (_jsx(StatusBadge, { tone: endpoint.confidence ?? "unknown", children: endpoint.confidence ?? "unknown" })) : null] }), _jsx("section", { className: "anlyx-endpoint-map", role: "region", "aria-label": "Endpoint Map", children: flow && model && model.nodes.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(FlowLegend, {}), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Endpoint map node list", children: nodes.map((node) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelectNode(node.data.node), children: ["Select node ", node.data.label] }) }, node.id))) }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Replay node state", children: nodes.map((node) => (_jsx("li", { "data-replay-active": String(Boolean(node.data.isReplayActive)), "data-testid": `replay-node-${node.id}`, children: node.id }, node.id))) }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Replay edge state", children: edges.map((edge) => (_jsxs("li", { "data-replay-active": String(Boolean(edge.data?.isReplayActive)), "data-testid": `replay-edge-${edge.source}-${edge.target}`, children: [edge.source, " to ", edge.target] }, edge.id))) }), _jsxs(ReactFlow, { className: "anlyx-react-flow", edges: edges, fitView: true, maxZoom: 1.25, minZoom: 0.45, nodes: nodes, nodesConnectable: false, nodesDraggable: false, nodeTypes: nodeTypes, onNodeClick: (_, node) => onSelectNode(node.data.node), panOnScroll: true, proOptions: { hideAttribution: true }, zoomOnDoubleClick: false, zoomOnScroll: false, children: [_jsx(Background, { color: "#dfe5ee", gap: 24, variant: BackgroundVariant.Dots }), _jsx(Controls, { showInteractive: false })] })] })) : (_jsx("div", { className: "anlyx-endpoint-map-empty", children: _jsx("p", { children: "No flow available for this endpoint yet." }) })) })] }));
76
+ return (_jsxs("main", { className: `anlyx-workspace anlyx-workspace--${variant}`, children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: eyebrow }), _jsx("h1", { children: title ?? (endpoint ? `${endpoint.method} ${endpoint.path}` : "No endpoint selected") })] }), _jsxs("div", { className: "anlyx-workspace-actions", children: [toolbar, endpoint ? (_jsx(StatusBadge, { tone: endpoint.confidence ?? "unknown", children: endpoint.confidence ?? "unknown" })) : null] })] }), _jsx("section", { className: `anlyx-endpoint-map anlyx-endpoint-map--${variant}`, role: "region", "aria-label": variant === "process" ? "Process Flow map" : "Endpoint Map", children: flow && model && model.nodes.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(FlowLegend, { variant: variant }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Endpoint map node list", children: nodes.map((node) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelectNode(node.data.node), children: ["Select node ", node.data.label] }) }, node.id))) }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Replay node state", children: nodes.map((node) => (_jsx("li", { "data-replay-active": String(Boolean(node.data.isReplayActive)), "data-testid": `replay-node-${node.id}`, children: node.id }, node.id))) }), _jsx("ul", { className: "anlyx-sr-only", "aria-label": "Replay edge state", children: edges.map((edge) => (_jsxs("li", { "data-replay-active": String(Boolean(edge.data?.isReplayActive)), "data-testid": `replay-edge-${edge.source}-${edge.target}`, children: [edge.source, " to ", edge.target] }, edge.id))) }), _jsxs(ReactFlow, { className: "anlyx-react-flow", edges: edges, fitView: true, fitViewOptions: { padding: 0.18 }, maxZoom: 1.35, minZoom: 0.62, nodes: nodes, nodesConnectable: false, nodesDraggable: false, edgeTypes: edgeTypes, nodeTypes: nodeTypes, onNodeClick: (_, node) => onSelectNode(node.data.node), panOnScroll: true, proOptions: { hideAttribution: true }, zoomOnDoubleClick: false, zoomOnScroll: false, children: [_jsx(Background, { color: "#dfe5ee", gap: 24, variant: BackgroundVariant.Dots }), _jsx(Controls, { showInteractive: false })] })] })) : (_jsx("div", { className: "anlyx-endpoint-map-empty", children: _jsx("p", { children: "No flow available for this endpoint yet." }) })) })] }));
77
+ }
78
+ function isUnitTestRuntime() {
79
+ return typeof process !== "undefined" && process.env.NODE_ENV === "test";
80
+ }
81
+ function isUsableLayout(model, variant) {
82
+ const positions = model.nodes.map((node) => node.position);
83
+ const minY = Math.min(...positions.map((position) => position.y));
84
+ const maxY = Math.max(...positions.map((position) => position.y));
85
+ const minX = Math.min(...positions.map((position) => position.x));
86
+ const maxX = Math.max(...positions.map((position) => position.x));
87
+ const height = maxY - minY;
88
+ const width = maxX - minX;
89
+ if (!Number.isFinite(height) || !Number.isFinite(width)) {
90
+ return false;
91
+ }
92
+ const maxHeight = variant === "process" ? 360 : 300;
93
+ return height <= maxHeight && width > 0;
94
+ }
95
+ function FlowEdgeLine({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, data }) {
96
+ const [edgePath] = getSmoothStepPath({
97
+ sourceX,
98
+ sourceY,
99
+ sourcePosition,
100
+ targetX,
101
+ targetY,
102
+ targetPosition
103
+ });
104
+ const isReplayActive = Boolean(data?.isReplayActive);
105
+ const flowRole = data?.flowRole ?? "secondary";
106
+ const particleRole = data?.replayPhase === "response" ? "response" : flowRole;
107
+ const className = [
108
+ "anlyx-flow-edge",
109
+ `anlyx-flow-edge--${flowRole}`,
110
+ isReplayActive ? "anlyx-flow-edge--replay-active" : "",
111
+ isReplayActive && data?.replayPhase === "response"
112
+ ? "anlyx-flow-edge--replay-response"
113
+ : isReplayActive
114
+ ? "anlyx-flow-edge--replay-request"
115
+ : ""
116
+ ]
117
+ .filter(Boolean)
118
+ .join(" ");
119
+ return (_jsxs(_Fragment, { children: [_jsx(BaseEdge, { className: className, path: edgePath, style: style }), isReplayActive ? (_jsx("circle", { className: `anlyx-flow-particle anlyx-flow-particle--${particleRole}`, r: flowRole === "sub" ? 4 : 5, children: _jsx("animateMotion", { dur: "1.25s", repeatCount: "indefinite", path: edgePath }) })) : null] }));
39
120
  }
40
121
  function isReplayEdgeActive(edge, activeEdge) {
41
122
  if (!activeEdge) {
@@ -44,4 +125,3 @@ function isReplayEdgeActive(edge, activeEdge) {
44
125
  return ((edge.source === activeEdge.from && edge.target === activeEdge.to) ||
45
126
  (edge.source === activeEdge.to && edge.target === activeEdge.from));
46
127
  }
47
- //# sourceMappingURL=EndpointMapCanvas.js.map
@@ -1,2 +1,4 @@
1
- export declare function FlowLegend(): JSX.Element;
2
- //# sourceMappingURL=FlowLegend.d.ts.map
1
+ export type FlowLegendProps = {
2
+ variant?: "structure" | "process";
3
+ };
4
+ export declare function FlowLegend({ variant }: FlowLegendProps): JSX.Element;
@@ -1,5 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export function FlowLegend() {
2
+ export function FlowLegend({ variant = "structure" }) {
3
+ if (variant === "process") {
4
+ return (_jsxs("div", { className: "anlyx-flow-legend", "aria-label": "Flow legend", children: [_jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--main" }), "Request"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--response" }), "Response"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--sub" }), "Branch calls"] })] }));
5
+ }
3
6
  return (_jsxs("div", { className: "anlyx-flow-legend", "aria-label": "Flow legend", children: [_jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--main" }), "Main Flow"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--sub" }), "Sub Flow"] }), _jsxs("span", { children: [_jsx("i", { className: "anlyx-flow-legend__mark anlyx-flow-legend__mark--unknown" }), "Unknown"] })] }));
4
7
  }
5
- //# sourceMappingURL=FlowLegend.js.map
@@ -1,4 +1,3 @@
1
- import type { NodeProps } from "@xyflow/react";
1
+ import { type NodeProps } from "@xyflow/react";
2
2
  import type { AnlyxReactFlowNode } from "../flow/build-react-flow-model.js";
3
3
  export declare function FlowNodeCard({ data, selected }: NodeProps<AnlyxReactFlowNode>): JSX.Element;
4
- //# sourceMappingURL=FlowNodeCard.d.ts.map
@@ -1,13 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Handle, Position } from "@xyflow/react";
3
+ import { Box, Braces, Code2, Database, GitBranch, Globe2, Layers3 } from "lucide-react";
4
+ import { motion } from "motion/react";
2
5
  import { StatusBadge } from "./StatusBadge.js";
3
6
  export function FlowNodeCard({ data, selected }) {
4
7
  const confidence = data.confidence ?? "unknown";
5
- return (_jsxs("button", { className: [
8
+ const Icon = getTypeIcon(data.type, data.flowRole);
9
+ return (_jsxs(motion.button, { animate: data.isReplayActive ? { scale: [1, 1.018, 1] } : { scale: 1 }, className: [
6
10
  "anlyx-flow-node",
7
11
  `anlyx-flow-node--${data.type}`,
8
12
  `anlyx-flow-node--${data.flowRole}`,
9
13
  data.isReplayActive ? "anlyx-flow-node--replay-active" : "",
10
14
  selected ? "anlyx-flow-node--selected" : ""
11
- ].join(" "), onClick: () => data.onSelectNode?.(data.node), type: "button", "aria-label": `Select node ${data.label}`, "data-replay-active": String(Boolean(data.isReplayActive)), "data-testid": `flow-node-${data.node.id}`, children: [_jsx("span", { className: "anlyx-flow-node__type", children: data.type }), _jsx("span", { className: "anlyx-flow-node__label", children: data.label }), _jsx(StatusBadge, { tone: confidence, label: "confidence", children: confidence })] }));
15
+ ].join(" "), onClick: () => data.onSelectNode?.(data.node), type: "button", "aria-label": `Select node ${data.label}`, "data-replay-active": String(Boolean(data.isReplayActive)), "data-testid": `flow-node-${data.node.id}`, transition: { duration: 1.35, repeat: data.isReplayActive ? Infinity : 0 }, children: [_jsx(Handle, { className: "anlyx-flow-handle", position: Position.Left, type: "target" }), data.isReplayActive ? _jsx("span", { className: "anlyx-flow-node__pulse", "aria-hidden": "true" }) : null, _jsxs("span", { className: "anlyx-flow-node__header", children: [_jsx("span", { className: "anlyx-flow-node__icon", "aria-hidden": "true", children: _jsx(Icon, { size: 14, strokeWidth: 2.4 }) }), _jsx("span", { className: "anlyx-flow-node__type", children: data.type })] }), _jsx("span", { className: "anlyx-flow-node__label", children: data.label }), _jsx(StatusBadge, { tone: confidence, label: "confidence", children: confidence }), _jsx(Handle, { className: "anlyx-flow-handle", position: Position.Right, type: "source" })] }));
16
+ }
17
+ function getTypeIcon(type, flowRole) {
18
+ if (flowRole === "sub") {
19
+ return GitBranch;
20
+ }
21
+ switch (type) {
22
+ case "endpoint":
23
+ return Globe2;
24
+ case "controller":
25
+ return Code2;
26
+ case "service":
27
+ return Layers3;
28
+ case "repository":
29
+ return Braces;
30
+ case "database":
31
+ return Database;
32
+ default:
33
+ return Box;
34
+ }
12
35
  }
13
- //# sourceMappingURL=FlowNodeCard.js.map
@@ -1,9 +1,14 @@
1
- import type { EndpointFlow, FlowNode, ScanResult } from "@anlyx/core";
1
+ import type { EndpointFlow, FlowNode, PageStoryboard, ScanResult } from "@anlyx/core";
2
+ import type { ReplayLiteState } from "../replay/use-replay-lite.js";
2
3
  type InspectorPanelProps = {
3
4
  data: ScanResult;
5
+ activeView: "structure" | "frontend" | "process";
6
+ collapsed: boolean;
4
7
  selectedFlow: EndpointFlow | undefined;
5
8
  selectedNode: FlowNode | undefined;
9
+ selectedPage: PageStoryboard | undefined;
10
+ replayState: ReplayLiteState;
11
+ onToggleCollapsed: () => void;
6
12
  };
7
- export declare function InspectorPanel({ data, selectedFlow, selectedNode }: InspectorPanelProps): JSX.Element;
13
+ export declare function InspectorPanel({ data, activeView, collapsed, selectedFlow, selectedNode, selectedPage, replayState, onToggleCollapsed }: InspectorPanelProps): JSX.Element;
8
14
  export {};
9
- //# sourceMappingURL=InspectorPanel.d.ts.map
@@ -1,8 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { StatusBadge } from "./StatusBadge.js";
3
- export function InspectorPanel({ data, selectedFlow, selectedNode }) {
3
+ export function InspectorPanel({ data, activeView, collapsed, selectedFlow, selectedNode, selectedPage, replayState, onToggleCollapsed }) {
4
+ if (collapsed) {
5
+ return (_jsxs("aside", { className: "anlyx-inspector anlyx-inspector--collapsed", role: "complementary", "aria-label": "Inspector", children: [_jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Expand inspector panel", onClick: onToggleCollapsed, children: "Open" }), _jsx("span", { className: "anlyx-collapsed-label", children: "Inspector" })] }));
6
+ }
7
+ if (activeView === "frontend") {
8
+ return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: "Frontend Page" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse inspector panel", onClick: onToggleCollapsed, children: "Collapse" })] }), selectedPage ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Details", children: [_jsx("h3", { children: "Details" }), _jsx(Field, { label: "Route", value: selectedPage.route }), _jsx(Field, { label: "File path", value: selectedPage.filePath ?? "Manual or unknown" }), _jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: "Capture status" }), _jsx(StatusBadge, { tone: selectedPage.captureStatus, children: selectedPage.captureStatus })] }), _jsx(Field, { label: "Screenshots", value: String(selectedPage.screenshots.length) }), _jsx(Field, { label: "API calls", value: String(selectedPage.apiCalls.length) })] }), selectedPage.errorMessage ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Capture error", children: [_jsx("h3", { children: "Capture error" }), _jsx("p", { children: selectedPage.errorMessage })] })) : null] })) : (_jsx("p", { className: "anlyx-empty", children: "No page selected" }))] }));
9
+ }
4
10
  const linkedPages = selectedNode ? findLinkedPages(data, selectedNode.id) : [];
5
- return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: "Selected Node" })] }), selectedNode ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [_jsx(Field, { label: "Type", value: selectedNode.type }), _jsx(Field, { label: "Label", value: selectedNode.label }), _jsx(Field, { label: "File path", value: selectedNode.filePath ?? "Unknown" }), _jsx(Field, { label: "Line number", value: formatLineNumber(selectedNode.lineNumber) }), selectedNode.metadata ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Metadata", children: [_jsx("h3", { children: "Metadata" }), _jsx("pre", { className: "anlyx-metadata", children: JSON.stringify(selectedNode.metadata, null, 2) })] })) : null, _jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: "Confidence" }), _jsx(StatusBadge, { tone: selectedNode.confidence ?? "unknown", label: "confidence", children: selectedNode.confidence ?? "unknown" })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Linked pages", children: [_jsx("h3", { children: "Linked pages" }), linkedPages.length > 0 ? (_jsx("ul", { children: linkedPages.map((page) => (_jsx("li", { children: page.route }, page.id))) })) : (_jsx("p", { children: "None" }))] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Sub flows", children: [_jsx("h3", { children: "Sub flows" }), _jsxs("p", { children: [selectedFlow?.subFlows.length ?? 0, " collapsed"] })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "DB tables", children: [_jsx("h3", { children: "DB tables" }), _jsx("p", { children: findDatabaseLabel(selectedFlow) ?? "None" })] })] })) : (_jsx("p", { className: "anlyx-empty", children: "No node selected" }))] }));
11
+ return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: activeView === "process" ? "Process Step" : "Backend Node" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse inspector panel", onClick: onToggleCollapsed, children: "Collapse" })] }), selectedNode ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [activeView === "process" ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Replay state", children: [_jsx("h3", { children: "Process replay" }), _jsx(Field, { label: "Active Step", value: replayState.phase }), _jsx(Field, { label: "Step", value: String(replayState.currentStepIndex + 1) }), _jsx(Field, { label: "Active Node", value: replayState.activeNodeId ?? "none" }), _jsx(Field, { label: "Active Edge", value: formatActiveEdge(replayState) }), _jsx("p", { className: "anlyx-inspector-note", children: "Source: scanned static flow graph" })] })) : null, _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Details", children: [_jsx("h3", { children: "Details" }), _jsx(Field, { label: "Type", value: selectedNode.type }), _jsx(Field, { label: "Label", value: selectedNode.label }), _jsx(Field, { label: "File path", value: selectedNode.filePath ?? "Unknown" }), _jsx(Field, { label: "Line number", value: formatLineNumber(selectedNode.lineNumber) })] }), selectedNode.metadata ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Metadata", children: [_jsxs("div", { className: "anlyx-inspector-group__heading", children: [_jsx("h3", { children: "Metadata" }), _jsx("button", { className: "anlyx-copy-button", type: "button", onClick: () => copyToClipboard(JSON.stringify(selectedNode.metadata, null, 2)), children: "Copy" })] }), _jsx("pre", { className: "anlyx-metadata", children: JSON.stringify(selectedNode.metadata, null, 2) })] })) : null, _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Confidence", children: [_jsx("h3", { children: "Confidence" }), _jsx(StatusBadge, { tone: selectedNode.confidence ?? "unknown", label: "confidence", children: selectedNode.confidence ?? "unknown" })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Linked pages", children: [_jsx("h3", { children: "Linked pages" }), linkedPages.length > 0 ? (_jsx("ul", { children: linkedPages.map((page) => (_jsx("li", { children: page.route }, page.id))) })) : (_jsx("p", { children: "None" }))] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Sub flows", children: [_jsx("h3", { children: "Sub flows" }), _jsxs("p", { children: [selectedFlow?.subFlows.length ?? 0, " collapsed"] })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "DB tables", children: [_jsx("h3", { children: "DB tables" }), _jsx("p", { children: findDatabaseLabel(selectedFlow) ?? "None" })] })] })) : (_jsx("p", { className: "anlyx-empty", children: "No node selected" }))] }));
6
12
  }
7
13
  function Field({ label, value }) {
8
14
  return (_jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: label }), _jsx("span", { className: "anlyx-field__value", children: value })] }));
@@ -10,10 +16,18 @@ function Field({ label, value }) {
10
16
  function formatLineNumber(lineNumber) {
11
17
  return lineNumber === undefined ? "Unknown" : String(lineNumber);
12
18
  }
19
+ function formatActiveEdge(replayState) {
20
+ if (!replayState.activeEdge) {
21
+ return "none";
22
+ }
23
+ return `${replayState.activeEdge.from} -> ${replayState.activeEdge.to}`;
24
+ }
25
+ function copyToClipboard(value) {
26
+ void navigator.clipboard?.writeText(value);
27
+ }
13
28
  function findLinkedPages(data, nodeId) {
14
29
  return data.pages.filter((page) => page.apiCalls.some((apiCall) => apiCall.endpointId === nodeId));
15
30
  }
16
31
  function findDatabaseLabel(flow) {
17
32
  return flow?.nodes.find((node) => node.type === "database")?.label;
18
33
  }
19
- //# sourceMappingURL=InspectorPanel.js.map
@@ -6,4 +6,3 @@ type PageListProps = {
6
6
  };
7
7
  export declare function PageList({ pages, selectedPageId, onSelectPage }: PageListProps): JSX.Element;
8
8
  export {};
9
- //# sourceMappingURL=PageList.d.ts.map
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { StatusBadge } from "./StatusBadge.js";
3
3
  export function PageList({ pages, selectedPageId, onSelectPage }) {
4
- return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-pages-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-pages-heading", children: "Pages" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Page list", children: pages.map((page) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-page-button", type: "button", "aria-current": page.id === selectedPageId ? "true" : undefined, "aria-label": `${page.route} ${page.captureStatus} ${page.apiCalls.length} API calls ${page.screenshots.length} screenshots`, onClick: () => onSelectPage(page), children: [_jsx("span", { className: "anlyx-list-item__line", children: _jsx("span", { className: "anlyx-list-item__primary", children: page.route }) }), _jsxs("span", { className: "anlyx-list-item__meta", children: [_jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }), _jsxs("span", { children: [page.apiCalls.length, " API calls"] }), _jsxs("span", { children: [page.screenshots.length, " screenshots"] })] }), page.errorMessage ? (_jsx("span", { className: "anlyx-list-item__meta", children: page.errorMessage })) : null] }) }, page.id))) })] }));
4
+ return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-pages-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-pages-heading", children: "Frontend Pages" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Page list", children: pages.map((page) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-page-button", type: "button", "aria-current": page.id === selectedPageId ? "true" : undefined, "aria-label": `${page.route} ${page.captureStatus} ${page.apiCalls.length} API calls ${page.screenshots.length} screenshots`, onClick: () => onSelectPage(page), children: [_jsx("span", { className: "anlyx-list-item__line", children: _jsx("span", { className: "anlyx-list-item__primary", children: page.route }) }), _jsxs("span", { className: "anlyx-list-item__meta", children: [_jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }), _jsxs("span", { children: [page.apiCalls.length, " API calls"] }), _jsxs("span", { children: [page.screenshots.length, " screenshots"] })] }), page.errorMessage ? (_jsx("span", { className: "anlyx-list-item__meta", children: page.errorMessage })) : null] }) }, page.id))) })] }));
5
5
  }
6
- //# sourceMappingURL=PageList.js.map
@@ -3,4 +3,3 @@ export type PageStoryboardCardProps = {
3
3
  page: PageStoryboard;
4
4
  };
5
5
  export declare function PageStoryboardCard({ page }: PageStoryboardCardProps): JSX.Element;
6
- //# sourceMappingURL=PageStoryboardCard.d.ts.map