@anlyx/ui 0.1.0 → 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 +11 -7
  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
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ScreenshotSegmentCard } from "./ScreenshotSegmentCard.js";
3
3
  export function PageStoryboardCard({ page }) {
4
- return (_jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "Screenshot segments", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "Segments" }), _jsx("span", { children: page.screenshots.length })] }), page.screenshots.length > 0 ? (_jsx("div", { className: "anlyx-segment-grid", children: page.screenshots.map((segment) => (_jsx(ScreenshotSegmentCard, { segment: segment }, `${page.id}:segment:${segment.segmentIndex}`))) })) : (_jsx("p", { className: "anlyx-empty-inline", children: "No screenshots captured yet." }))] }));
4
+ return (_jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "Screenshot segments", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "Segments" }), _jsx("span", { children: page.screenshots.length })] }), page.screenshots.length > 0 ? (_jsx("div", { className: "anlyx-segment-grid", children: page.screenshots.map((segment) => (_jsx(ScreenshotSegmentCard, { segment: segment }, `${page.id}:segment:${segment.segmentIndex}`))) })) : (_jsxs("div", { className: "anlyx-segment-grid anlyx-segment-grid--empty", children: [_jsx(PlaceholderSegment, { index: 1, title: "Overview" }), _jsx(PlaceholderSegment, { index: 2, title: "Primary content" }), _jsx(PlaceholderSegment, { index: 3, title: "Related sections" })] }))] }));
5
+ }
6
+ function PlaceholderSegment({ index, title }) {
7
+ return (_jsxs("article", { className: "anlyx-segment-card anlyx-segment-card--placeholder", children: [_jsx("div", { className: "anlyx-segment-card__image", "aria-hidden": "true", children: _jsx("span", { children: index }) }), _jsxs("div", { className: "anlyx-segment-card__body", children: [_jsx("h3", { children: title }), _jsx("p", { children: "Waiting for capture data" }), _jsxs("div", { className: "anlyx-segment-card__meta", children: [_jsx("span", { children: "Viewport pending" }), _jsx("span", { children: "Capture pending" })] })] })] }));
5
8
  }
6
- //# sourceMappingURL=PageStoryboardCard.js.map
@@ -1,6 +1,7 @@
1
- import type { PageStoryboard } from "@anlyx/core";
1
+ import type { PageStoryboard, ScanResult } from "@anlyx/core";
2
2
  export type PageStoryboardViewProps = {
3
+ data: ScanResult;
3
4
  page: PageStoryboard | undefined;
5
+ onViewProcessFlow: (view: "process") => void;
4
6
  };
5
- export declare function PageStoryboardView({ page }: PageStoryboardViewProps): JSX.Element;
6
- //# sourceMappingURL=PageStoryboardView.d.ts.map
7
+ export declare function PageStoryboardView({ data, page, onViewProcessFlow }: PageStoryboardViewProps): JSX.Element;
@@ -3,7 +3,9 @@ import { ApiCallList } from "./ApiCallList.js";
3
3
  import { CaptureStatusEmptyState } from "./CaptureStatusEmptyState.js";
4
4
  import { PageStoryboardCard } from "./PageStoryboardCard.js";
5
5
  import { StatusBadge } from "./StatusBadge.js";
6
- export function PageStoryboardView({ page }) {
7
- return (_jsxs("main", { className: "anlyx-workspace", children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Page Storyboard" }), _jsx("h1", { children: "Page Storyboard" })] }), page ? _jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }) : null] }), _jsx("section", { className: "anlyx-page-storyboard", role: "region", "aria-label": "Page Storyboard", children: page ? (_jsxs(_Fragment, { children: [_jsxs("section", { className: "anlyx-page-summary", "aria-label": "Selected page summary", children: [_jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Route" }), _jsx("h2", { children: page.route })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "File" }), _jsx("p", { children: page.filePath ?? "Unknown" })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Status" }), _jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus })] })] }), _jsx(CaptureStatusEmptyState, { status: page.captureStatus, ...(page.errorMessage ? { reason: page.errorMessage } : {}) }), _jsxs("div", { className: "anlyx-storyboard-grid", children: [_jsx(PageStoryboardCard, { page: page }), _jsx(ApiCallList, { apiCalls: page.apiCalls })] })] })) : (_jsx("div", { className: "anlyx-storyboard-empty", children: _jsx("p", { children: "No pages available yet." }) })) })] }));
6
+ export function PageStoryboardView({ data, page, onViewProcessFlow }) {
7
+ const linkedEndpoints = page
8
+ ? data.endpoints.filter((endpoint) => page.apiCalls.some((apiCall) => apiCall.endpointId === endpoint.id))
9
+ : [];
10
+ return (_jsxs("main", { className: "anlyx-workspace", children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Connected Frontend" }), _jsx("h1", { children: page ? page.route : "Page Storyboard" })] }), _jsxs("div", { className: "anlyx-workspace-actions", children: [_jsx("button", { className: "anlyx-toolbar-button", type: "button", disabled: linkedEndpoints.length === 0, onClick: () => onViewProcessFlow("process"), children: "View Process Flow" }), page ? _jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }) : null] })] }), _jsx("section", { className: "anlyx-page-storyboard", role: "region", "aria-label": "Page Storyboard", children: page ? (_jsxs(_Fragment, { children: [_jsxs("section", { className: "anlyx-page-summary", "aria-label": "Selected page summary", children: [_jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Route" }), _jsx("h2", { children: page.route })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "File" }), _jsx("p", { children: page.filePath ?? "Unknown" })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Status" }), _jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Linked endpoints" }), _jsx("p", { children: linkedEndpoints.length })] })] }), _jsx(CaptureStatusEmptyState, { status: page.captureStatus, ...(page.errorMessage ? { reason: page.errorMessage } : {}) }), _jsxs("div", { className: "anlyx-storyboard-grid", children: [_jsx(PageStoryboardCard, { page: page }), _jsxs("div", { className: "anlyx-storyboard-side", children: [_jsx(ApiCallList, { apiCalls: page.apiCalls }), _jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "Page to endpoint relationship", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "Page to Endpoint" }), _jsx("span", { children: linkedEndpoints.length })] }), linkedEndpoints.length > 0 ? (_jsxs("div", { className: "anlyx-relationship-diagram", children: [_jsxs("div", { className: "anlyx-relationship-source", children: [_jsx("strong", { children: page.route }), _jsx("span", { children: "Frontend Page" })] }), _jsx("ul", { className: "anlyx-relationship-list", children: linkedEndpoints.map((endpoint) => (_jsxs("li", { children: [_jsx("span", { className: "anlyx-relationship-line", "aria-hidden": "true" }), _jsx(StatusBadge, { tone: endpoint.method, children: endpoint.method }), _jsx("span", { children: endpoint.path })] }, endpoint.id))) })] })) : (_jsx("p", { className: "anlyx-empty-inline", children: "No backend endpoint was linked during capture." }))] })] })] })] })) : (_jsx("div", { className: "anlyx-storyboard-empty", children: _jsx("p", { children: "No pages available yet." }) })) })] }));
8
11
  }
9
- //# sourceMappingURL=PageStoryboardView.js.map
@@ -0,0 +1,21 @@
1
+ import type { Endpoint, EndpointFlow, FlowNode } from "@anlyx/core";
2
+ import type { ReplayLiteState } from "../replay/use-replay-lite.js";
3
+ import type { ReplayStep } from "../replay/build-replay-steps.js";
4
+ export type ProcessFlowViewProps = {
5
+ endpoint: Endpoint | undefined;
6
+ flow: EndpointFlow | undefined;
7
+ replayState: ReplayLiteState;
8
+ replaySteps: ReplayStep[];
9
+ replayLoop: boolean;
10
+ replayDisabled: boolean;
11
+ replaySpeed: number;
12
+ selectedNodeId: string | undefined;
13
+ onSelectNode: (node: FlowNode) => void;
14
+ onPause: () => void;
15
+ onPlay: () => void;
16
+ onRestart: () => void;
17
+ onToggleLoop: () => void;
18
+ onSpeedChange: (speed: number) => void;
19
+ onViewStructure: () => void;
20
+ };
21
+ export declare function ProcessFlowView({ endpoint, flow, replayState, replaySteps, replayLoop, replayDisabled, replaySpeed, selectedNodeId, onSelectNode, onPause, onPlay, onRestart, onToggleLoop, onSpeedChange, onViewStructure }: ProcessFlowViewProps): JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EndpointMapCanvas } from "./EndpointMapCanvas.js";
3
+ import { ProcessTimeline } from "./ProcessTimeline.js";
4
+ import { ReplayControls } from "./ReplayControls.js";
5
+ export function ProcessFlowView({ endpoint, flow, replayState, replaySteps, replayLoop, replayDisabled, replaySpeed, selectedNodeId, onSelectNode, onPause, onPlay, onRestart, onToggleLoop, onSpeedChange, onViewStructure }) {
6
+ return (_jsxs("div", { className: "anlyx-process-view", children: [_jsx(ReplayControls, { disabled: replayDisabled, loop: replayLoop, speed: replaySpeed, state: replayState, steps: replaySteps, unavailableReason: "Process Flow is unavailable because this endpoint has no scanned main path.", onPause: onPause, onPlay: onPlay, onRestart: onRestart, onSpeedChange: onSpeedChange, onToggleLoop: onToggleLoop }), _jsx(EndpointMapCanvas, { eyebrow: "Request Process Flow", endpoint: endpoint, flow: flow, replayState: replayState, selectedNodeId: selectedNodeId, title: endpoint
7
+ ? `${endpoint.method} ${endpoint.path}`
8
+ : "Request process flow from scanned graph", toolbar: _jsx("button", { className: "anlyx-toolbar-button", type: "button", onClick: onViewStructure, children: "View on Structure" }), variant: "process", onSelectNode: onSelectNode }), _jsx(ProcessTimeline, { flow: flow, state: replayState, steps: replaySteps })] }));
9
+ }
@@ -0,0 +1,9 @@
1
+ import type { EndpointFlow } from "@anlyx/core";
2
+ import type { ReplayStep } from "../replay/build-replay-steps.js";
3
+ import type { ReplayLiteState } from "../replay/use-replay-lite.js";
4
+ export type ProcessTimelineProps = {
5
+ flow: EndpointFlow | undefined;
6
+ steps: ReplayStep[];
7
+ state: ReplayLiteState;
8
+ };
9
+ export declare function ProcessTimeline({ flow, steps, state }: ProcessTimelineProps): JSX.Element;
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Check } from "lucide-react";
3
+ import { motion } from "motion/react";
4
+ export function ProcessTimeline({ flow, steps, state }) {
5
+ const nodeById = new Map(flow?.nodes.map((node) => [node.id, node]) ?? []);
6
+ const isComplete = state.phase === "complete";
7
+ return (_jsxs("section", { className: "anlyx-process-timeline", "aria-label": "Process Flow timeline", children: [_jsxs("div", { className: "anlyx-process-timeline__header", children: [_jsx("span", { children: "Inferred request path" }), _jsxs("span", { children: [steps.length, " replay steps"] })] }), _jsxs("ol", { className: "anlyx-process-timeline__rail", children: [steps.map((step, index) => {
8
+ const node = nodeById.get(step.nodeId);
9
+ const isActive = state.phase !== "idle" &&
10
+ state.phase !== "complete" &&
11
+ state.currentStepIndex === index;
12
+ const isCompleted = state.phase === "complete" ||
13
+ (state.phase !== "idle" && state.currentStepIndex > index);
14
+ return (_jsxs("li", { className: [
15
+ "anlyx-process-step",
16
+ `anlyx-process-step--${step.phase}`,
17
+ isActive ? "anlyx-process-step--active" : "",
18
+ isCompleted ? "anlyx-process-step--complete" : ""
19
+ ]
20
+ .filter(Boolean)
21
+ .join(" "), children: [_jsx(motion.span, { animate: isActive ? { scale: [1, 1.18, 1] } : { scale: 1 }, className: "anlyx-process-step__dot", "aria-hidden": "true", transition: { duration: 1.1, repeat: isActive ? Infinity : 0 }, children: isCompleted ? _jsx(Check, { size: 11, strokeWidth: 3 }) : null }), _jsx("span", { className: "anlyx-process-step__phase", children: step.phase === "request" ? "Request" : "Response" }), _jsx("span", { className: "anlyx-process-step__label", children: formatStepLabel(node?.type, step.phase) }), _jsx("span", { className: "anlyx-process-step__node", children: node?.label ?? step.nodeId })] }, `${step.phase}:${step.nodeId}:${index}`));
22
+ }), _jsxs("li", { className: [
23
+ "anlyx-process-step",
24
+ "anlyx-process-step--complete-stop",
25
+ isComplete ? "anlyx-process-step--active" : ""
26
+ ]
27
+ .filter(Boolean)
28
+ .join(" "), children: [_jsx(motion.span, { animate: isComplete ? { scale: [1, 1.18, 1] } : { scale: 1 }, className: "anlyx-process-step__dot", "aria-hidden": "true", transition: { duration: 1.1, repeat: isComplete ? Infinity : 0 }, children: isComplete ? _jsx(Check, { size: 11, strokeWidth: 3 }) : null }), _jsx("span", { className: "anlyx-process-step__phase", children: "Complete" }), _jsx("span", { className: "anlyx-process-step__label", children: "Client" }), _jsx("span", { className: "anlyx-process-step__node", children: "Response delivered" })] })] })] }));
29
+ }
30
+ function formatStepLabel(type, phase) {
31
+ const suffix = phase === "response" ? " Return" : "";
32
+ switch (type) {
33
+ case "endpoint":
34
+ return phase === "response" ? "Endpoint Return" : "Endpoint";
35
+ case "controller":
36
+ return `Controller${suffix}`;
37
+ case "service":
38
+ return `Service${suffix}`;
39
+ case "repository":
40
+ return `Repository${suffix}`;
41
+ case "database":
42
+ return phase === "response" ? "Database Result" : "Database";
43
+ default:
44
+ return "Flow Node";
45
+ }
46
+ }
@@ -1,13 +1,16 @@
1
1
  import type { ReplayLiteState } from "../replay/use-replay-lite.js";
2
+ import type { ReplayStep } from "../replay/build-replay-steps.js";
2
3
  export type ReplayControlsProps = {
3
4
  state: ReplayLiteState;
5
+ steps: ReplayStep[];
4
6
  loop: boolean;
5
7
  disabled?: boolean;
8
+ speed: number;
6
9
  unavailableReason?: string;
7
10
  onPlay: () => void;
8
11
  onPause: () => void;
9
12
  onRestart: () => void;
10
13
  onToggleLoop: () => void;
14
+ onSpeedChange: (speed: number) => void;
11
15
  };
12
- export declare function ReplayControls({ state, loop, disabled, unavailableReason, onPlay, onPause, onRestart, onToggleLoop }: ReplayControlsProps): JSX.Element;
13
- //# sourceMappingURL=ReplayControls.d.ts.map
16
+ export declare function ReplayControls({ state, steps, loop, speed, disabled, unavailableReason, onPlay, onPause, onRestart, onToggleLoop, onSpeedChange }: ReplayControlsProps): JSX.Element;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export function ReplayControls({ state, loop, disabled = false, unavailableReason, onPlay, onPause, onRestart, onToggleLoop }) {
3
- return (_jsxs("section", { className: "anlyx-replay", "aria-label": "Replay Lite controls", children: [_jsxs("div", { className: "anlyx-replay__buttons", "aria-label": "Replay actions", children: [_jsx("button", { type: "button", disabled: disabled || state.isPlaying, onClick: onPlay, children: "Play" }), _jsx("button", { type: "button", disabled: disabled || !state.isPlaying, onClick: onPause, children: "Pause" }), _jsx("button", { type: "button", disabled: disabled, onClick: onRestart, children: "Restart" }), _jsxs("button", { type: "button", "aria-pressed": loop, disabled: disabled, onClick: onToggleLoop, children: ["Loop ", loop ? "on" : "off"] })] }), _jsxs("div", { className: "anlyx-replay__state", children: [_jsxs("span", { children: ["Phase: ", state.phase] }), _jsxs("span", { children: ["Active: ", state.activeNodeId ?? "none"] }), _jsx("span", { children: "Main Flow only" })] }), disabled && unavailableReason ? (_jsx("p", { className: "anlyx-replay__empty", children: unavailableReason })) : null] }));
2
+ import { Pause, Play, RefreshCw, Repeat2 } from "lucide-react";
3
+ export function ReplayControls({ state, steps, loop, speed, disabled = false, unavailableReason, onPlay, onPause, onRestart, onToggleLoop, onSpeedChange }) {
4
+ const currentStepNumber = state.phase === "idle" ? 0 : Math.min(state.currentStepIndex + 1, steps.length);
5
+ return (_jsxs("section", { className: "anlyx-replay", "aria-label": "Process Flow controls", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Replay from scanned flow graph" }), _jsxs("div", { className: "anlyx-replay__buttons", "aria-label": "Process Flow actions", children: [_jsxs("button", { className: "anlyx-replay__button--primary", type: "button", disabled: disabled || state.isPlaying, onClick: onPlay, children: [_jsx(Play, { size: 14, strokeWidth: 2.5 }), _jsx("span", { children: "Play" })] }), _jsxs("button", { type: "button", disabled: disabled || !state.isPlaying, onClick: onPause, children: [_jsx(Pause, { size: 14, strokeWidth: 2.5 }), _jsx("span", { children: "Pause" })] }), _jsxs("button", { type: "button", disabled: disabled, onClick: onRestart, children: [_jsx(RefreshCw, { size: 14, strokeWidth: 2.5 }), _jsx("span", { children: "Restart" })] }), _jsxs("button", { type: "button", "aria-pressed": loop, disabled: disabled, onClick: onToggleLoop, children: [_jsx(Repeat2, { size: 14, strokeWidth: 2.5 }), _jsxs("span", { children: ["Loop ", loop ? "on" : "off"] })] })] })] }), _jsxs("div", { className: "anlyx-replay__state", children: [_jsxs("span", { children: ["Step ", currentStepNumber, "/", steps.length] }), _jsxs("span", { children: ["Phase: ", state.phase] }), _jsxs("label", { children: ["Speed", _jsxs("select", { "aria-label": "Replay speed", disabled: disabled, value: speed, onChange: (event) => onSpeedChange(Number(event.currentTarget.value)), children: [_jsx("option", { value: 1100, children: "0.75x" }), _jsx("option", { value: 800, children: "1x" }), _jsx("option", { value: 520, children: "1.5x" })] })] }), _jsx("span", { children: "Main Flow only" })] }), disabled && unavailableReason ? (_jsx("p", { className: "anlyx-replay__empty", children: unavailableReason })) : null] }));
4
6
  }
5
- //# sourceMappingURL=ReplayControls.js.map
@@ -3,4 +3,3 @@ export type ScreenshotSegmentCardProps = {
3
3
  segment: ScreenshotSegment;
4
4
  };
5
5
  export declare function ScreenshotSegmentCard({ segment }: ScreenshotSegmentCardProps): JSX.Element;
6
- //# sourceMappingURL=ScreenshotSegmentCard.d.ts.map
@@ -3,4 +3,3 @@ export function ScreenshotSegmentCard({ segment }) {
3
3
  const title = segment.title ?? `Segment ${segment.segmentIndex + 1}`;
4
4
  return (_jsxs("article", { className: "anlyx-segment-card", children: [_jsx("div", { className: "anlyx-segment-card__image", children: segment.path ? _jsx("img", { alt: title, src: segment.path }) : _jsx("span", { children: "No image path" }) }), _jsxs("div", { className: "anlyx-segment-card__body", children: [_jsxs("h3", { children: ["Segment ", segment.segmentIndex + 1] }), segment.title ? _jsx("p", { children: segment.title }) : null, segment.path ? _jsx("code", { children: segment.path }) : _jsx("code", { children: "No path" }), _jsxs("div", { className: "anlyx-segment-card__meta", children: [_jsxs("span", { children: ["scrollY ", segment.scrollY] }), _jsxs("span", { children: [segment.viewport.width, " x ", segment.viewport.height] })] })] })] }));
5
5
  }
6
- //# sourceMappingURL=ScreenshotSegmentCard.js.map
@@ -1,13 +1,14 @@
1
1
  import type { Endpoint, PageStoryboard, ScanResult } from "@anlyx/core";
2
2
  type SidebarProps = {
3
3
  data: ScanResult;
4
- activeView: "endpoint" | "pages" | "replay";
4
+ activeView: "structure" | "frontend" | "process";
5
+ collapsed: boolean;
5
6
  selectedEndpointId: string | undefined;
6
7
  selectedPageId: string | undefined;
7
- onSelectView: (view: "endpoint" | "pages" | "replay") => void;
8
+ onSelectView: (view: "structure" | "frontend" | "process") => void;
9
+ onToggleCollapsed: () => void;
8
10
  onSelectEndpoint: (endpoint: Endpoint) => void;
9
11
  onSelectPage: (page: PageStoryboard) => void;
10
12
  };
11
- export declare function Sidebar({ data, activeView, selectedEndpointId, selectedPageId, onSelectView, onSelectEndpoint, onSelectPage }: SidebarProps): JSX.Element;
13
+ export declare function Sidebar({ data, activeView, collapsed, selectedEndpointId, selectedPageId, onSelectView, onToggleCollapsed, onSelectEndpoint, onSelectPage }: SidebarProps): JSX.Element;
12
14
  export {};
13
- //# sourceMappingURL=Sidebar.d.ts.map
@@ -1,7 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PanelLeftClose, PanelLeftOpen, Search } from "lucide-react";
2
3
  import { EndpointList } from "./EndpointList.js";
3
4
  import { PageList } from "./PageList.js";
4
- export function Sidebar({ data, activeView, selectedEndpointId, selectedPageId, onSelectView, onSelectEndpoint, onSelectPage }) {
5
- return (_jsxs("aside", { className: "anlyx-sidebar", "aria-label": "Primary navigation", children: [_jsxs("div", { className: "anlyx-brand", children: [_jsx("div", { className: "anlyx-brand__mark", "aria-hidden": "true", children: "A" }), _jsxs("div", { children: [_jsx("div", { className: "anlyx-brand__name", children: "Anlyx" }), _jsx("div", { className: "anlyx-brand__project", children: data.projectName })] })] }), _jsxs("nav", { className: "anlyx-tabs", "aria-label": "Views", children: [_jsx("button", { className: activeView === "endpoint" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("endpoint"), children: "Endpoint" }), _jsx("button", { className: activeView === "pages" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("pages"), children: "Pages" }), _jsx("button", { className: activeView === "replay" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("replay"), children: "Replay" })] }), _jsxs("label", { className: "anlyx-search", children: [_jsx("span", { className: "anlyx-search__label", children: "Search" }), _jsx("input", { placeholder: "Search endpoints or pages", type: "search" })] }), _jsx(EndpointList, { endpoints: data.endpoints, selectedEndpointId: selectedEndpointId, onSelectEndpoint: onSelectEndpoint }), _jsx(PageList, { pages: data.pages, selectedPageId: selectedPageId, onSelectPage: onSelectPage })] }));
5
+ export function Sidebar({ data, activeView, collapsed, selectedEndpointId, selectedPageId, onSelectView, onToggleCollapsed, onSelectEndpoint, onSelectPage }) {
6
+ if (collapsed) {
7
+ return (_jsxs("aside", { className: "anlyx-sidebar anlyx-sidebar--collapsed", "aria-label": "Primary navigation", children: [_jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Expand navigation panel", onClick: onToggleCollapsed, children: _jsx(PanelLeftOpen, { size: 15, strokeWidth: 2.4 }) }), _jsx("span", { className: "anlyx-collapsed-label", children: "Nav" })] }));
8
+ }
9
+ return (_jsxs("aside", { className: "anlyx-sidebar", "aria-label": "Primary navigation", children: [_jsxs("div", { className: "anlyx-brand", children: [_jsx("div", { className: "anlyx-brand__mark", "aria-hidden": "true", children: "A" }), _jsxs("div", { children: [_jsx("div", { className: "anlyx-brand__name", children: "Anlyx" }), _jsx("div", { className: "anlyx-brand__project", children: data.projectName })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse navigation panel", onClick: onToggleCollapsed, children: _jsx(PanelLeftClose, { size: 15, strokeWidth: 2.4 }) })] }), _jsxs("nav", { className: "anlyx-tabs", "aria-label": "Views", children: [_jsx("button", { className: activeView === "structure" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("structure"), children: "Structure" }), _jsx("button", { className: activeView === "frontend" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("frontend"), children: "Connected Frontend" }), _jsx("button", { className: activeView === "process" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("process"), children: "Process Flow" })] }), _jsxs("label", { className: "anlyx-search", children: [_jsx("span", { className: "anlyx-search__label", children: "Search" }), _jsxs("span", { className: "anlyx-search__control", children: [_jsx(Search, { size: 14, strokeWidth: 2.4 }), _jsx("input", { placeholder: "Search endpoints or pages", type: "search" })] })] }), _jsx("div", { className: "anlyx-sidebar__list-region", children: activeView === "frontend" ? (_jsx(PageList, { pages: data.pages, selectedPageId: selectedPageId, onSelectPage: onSelectPage })) : (_jsx(EndpointList, { endpoints: data.endpoints, selectedEndpointId: selectedEndpointId, onSelectEndpoint: onSelectEndpoint })) })] }));
6
10
  }
7
- //# sourceMappingURL=Sidebar.js.map
@@ -6,4 +6,3 @@ type StatusBadgeProps = {
6
6
  };
7
7
  export declare function StatusBadge({ children, tone, label }: StatusBadgeProps): JSX.Element;
8
8
  export {};
9
- //# sourceMappingURL=StatusBadge.d.ts.map
@@ -2,4 +2,3 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export function StatusBadge({ children, tone = "neutral", label }) {
3
3
  return (_jsxs("span", { className: `anlyx-status-badge anlyx-status-badge--${tone.toLowerCase()}`, children: [label ? _jsx("span", { className: "anlyx-status-badge__label", children: label }) : null, children] }));
4
4
  }
5
- //# sourceMappingURL=StatusBadge.js.map
@@ -1,5 +1,6 @@
1
1
  import type { ConfidenceLevel, EndpointFlow, FlowEdge, FlowNode } from "@anlyx/core";
2
2
  import type { Edge, Node } from "@xyflow/react";
3
+ import { type FlowLayoutVariant } from "./layout/elk-layout.js";
3
4
  export type AnlyxFlowRole = "main" | "sub" | "secondary";
4
5
  export type AnlyxFlowNodeData = {
5
6
  node: FlowNode;
@@ -16,6 +17,7 @@ export type AnlyxFlowEdgeData = {
16
17
  flowRole: AnlyxFlowRole;
17
18
  confidence?: ConfidenceLevel;
18
19
  isReplayActive?: boolean;
20
+ replayPhase?: "request" | "response" | "idle" | "complete";
19
21
  };
20
22
  export type AnlyxReactFlowNode = Node<AnlyxFlowNodeData, "anlyxNode">;
21
23
  export type AnlyxReactFlowEdge = Edge<AnlyxFlowEdgeData>;
@@ -23,5 +25,7 @@ export type ReactFlowModel = {
23
25
  nodes: AnlyxReactFlowNode[];
24
26
  edges: AnlyxReactFlowEdge[];
25
27
  };
26
- export declare function buildReactFlowModel(flow: EndpointFlow): ReactFlowModel;
27
- //# sourceMappingURL=build-react-flow-model.d.ts.map
28
+ export type BuildReactFlowModelOptions = {
29
+ variant?: FlowLayoutVariant;
30
+ };
31
+ export declare function buildReactFlowModel(flow: EndpointFlow, options?: BuildReactFlowModelOptions): ReactFlowModel;
@@ -1,41 +1,39 @@
1
- const MAIN_X_SPACING = 220;
2
- const SECONDARY_Y_OFFSET = 120;
3
- const SUB_X_SPACING = 180;
4
- const SUB_Y_OFFSET = 180;
5
- export function buildReactFlowModel(flow) {
1
+ import { layoutWithFallback } from "./layout/elk-layout.js";
2
+ export function buildReactFlowModel(flow, options = {}) {
3
+ const variant = options.variant ?? "structure";
6
4
  const mainPathSet = new Set(flow.mainPath);
7
5
  const nodesById = new Map(flow.nodes.map((node) => [node.id, node]));
8
6
  const positionedNodes = new Map();
9
7
  flow.mainPath.forEach((nodeId, index) => {
10
8
  const node = nodesById.get(nodeId);
11
9
  if (node) {
12
- positionedNodes.set(node.id, createNode(node, "main", { x: index * MAIN_X_SPACING, y: 0 }));
10
+ positionedNodes.set(node.id, createNode(node, "main", { x: index * 220, y: 0 }));
13
11
  }
14
12
  });
15
13
  flow.nodes
16
14
  .filter((node) => !mainPathSet.has(node.id))
17
15
  .forEach((node, index) => {
18
- positionedNodes.set(node.id, createNode(node, "secondary", { x: index * MAIN_X_SPACING, y: SECONDARY_Y_OFFSET }));
16
+ positionedNodes.set(node.id, createNode(node, "secondary", { x: index * 220, y: 120 }));
19
17
  });
20
18
  flow.subFlows.forEach((subFlow, subFlowIndex) => {
21
19
  const parentPosition = positionedNodes.get(subFlow.parentNodeId)?.position ?? {
22
- x: subFlowIndex * MAIN_X_SPACING,
20
+ x: subFlowIndex * 220,
23
21
  y: 0
24
22
  };
25
23
  subFlow.nodes.forEach((node, index) => {
26
24
  positionedNodes.set(node.id, createNode(node, "sub", {
27
- x: parentPosition.x + index * SUB_X_SPACING,
28
- y: parentPosition.y + SUB_Y_OFFSET + subFlowIndex * 36
25
+ x: parentPosition.x + index * 180,
26
+ y: parentPosition.y + 180 + subFlowIndex * 36
29
27
  }, subFlow.id));
30
28
  });
31
29
  });
32
- return {
30
+ return layoutWithFallback({
33
31
  nodes: [...positionedNodes.values()],
34
32
  edges: [
35
- ...flow.edges.map((edge) => createEdge(edge, getEdgeRole(edge, mainPathSet))),
36
- ...flow.subFlows.flatMap((subFlow) => subFlow.edges.map((edge) => createEdge(edge, "sub")))
33
+ ...flow.edges.map((edge, index) => createEdge(edge, getEdgeRole(edge, mainPathSet), index)),
34
+ ...flow.subFlows.flatMap((subFlow, subFlowIndex) => subFlow.edges.map((edge, edgeIndex) => createEdge(edge, "sub", `${subFlowIndex}:${edgeIndex}`)))
37
35
  ]
38
- };
36
+ }, flow.mainPath, { variant });
39
37
  }
40
38
  function createNode(node, flowRole, position, subFlowId) {
41
39
  return {
@@ -52,12 +50,12 @@ function createNode(node, flowRole, position, subFlowId) {
52
50
  }
53
51
  };
54
52
  }
55
- function createEdge(edge, flowRole) {
53
+ function createEdge(edge, flowRole, stableSuffix) {
56
54
  return {
57
- id: edge.id,
55
+ id: `${edge.id}:${stableSuffix}`,
58
56
  source: edge.from,
59
57
  target: edge.to,
60
- type: "smoothstep",
58
+ type: "anlyxEdge",
61
59
  animated: false,
62
60
  className: `anlyx-flow-edge anlyx-flow-edge--${flowRole}`,
63
61
  data: {
@@ -76,4 +74,3 @@ function getEdgeRole(edge, mainPathSet) {
76
74
  }
77
75
  return "secondary";
78
76
  }
79
- //# sourceMappingURL=build-react-flow-model.js.map
@@ -0,0 +1,7 @@
1
+ import type { ReactFlowModel } from "../build-react-flow-model.js";
2
+ export type FlowLayoutVariant = "structure" | "process";
3
+ export type LayoutOptions = {
4
+ variant: FlowLayoutVariant;
5
+ };
6
+ export declare function layoutWithElk(model: ReactFlowModel, options: LayoutOptions): Promise<ReactFlowModel>;
7
+ export declare function layoutWithFallback(model: ReactFlowModel, mainPath: string[], options: LayoutOptions): ReactFlowModel;
@@ -0,0 +1,74 @@
1
+ import ELKConstructor from "elkjs/lib/elk.bundled.js";
2
+ const Elk = ELKConstructor;
3
+ const elk = new Elk();
4
+ const NODE_WIDTH = 196;
5
+ const NODE_HEIGHT = 92;
6
+ const STRUCTURE_MAIN_Y = 0;
7
+ const STRUCTURE_SECONDARY_Y = 145;
8
+ const PROCESS_REQUEST_Y = 0;
9
+ const PROCESS_BRANCH_Y = 145;
10
+ const MAIN_X_SPACING = 232;
11
+ const BRANCH_X_SPACING = 190;
12
+ export async function layoutWithElk(model, options) {
13
+ if (model.nodes.length === 0) {
14
+ return model;
15
+ }
16
+ const graph = {
17
+ id: "anlyx-flow",
18
+ layoutOptions: {
19
+ "elk.algorithm": "layered",
20
+ "elk.direction": "RIGHT",
21
+ "elk.layered.spacing.nodeNodeBetweenLayers": options.variant === "process" ? "72" : "84",
22
+ "elk.spacing.nodeNode": options.variant === "process" ? "44" : "52",
23
+ "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
24
+ "elk.layered.crossingMinimization.strategy": "LAYER_SWEEP"
25
+ },
26
+ children: model.nodes.map((node) => ({
27
+ id: node.id,
28
+ width: NODE_WIDTH,
29
+ height: NODE_HEIGHT
30
+ })),
31
+ edges: model.edges.map((edge) => ({
32
+ id: edge.id,
33
+ sources: [edge.source],
34
+ targets: [edge.target]
35
+ }))
36
+ };
37
+ const layoutedGraph = await elk.layout(graph);
38
+ const positions = new Map(layoutedGraph.children?.map((node) => [node.id, { x: node.x ?? 0, y: node.y ?? 0 }]) ?? []);
39
+ return {
40
+ nodes: model.nodes.map((node) => ({
41
+ ...node,
42
+ position: positions.get(node.id) ?? node.position
43
+ })),
44
+ edges: model.edges
45
+ };
46
+ }
47
+ export function layoutWithFallback(model, mainPath, options) {
48
+ const mainPathIndex = new Map(mainPath.map((nodeId, index) => [nodeId, index]));
49
+ let secondaryIndex = 0;
50
+ return {
51
+ nodes: model.nodes.map((node) => {
52
+ const pathIndex = mainPathIndex.get(node.id);
53
+ if (pathIndex !== undefined) {
54
+ return {
55
+ ...node,
56
+ position: {
57
+ x: pathIndex * MAIN_X_SPACING,
58
+ y: options.variant === "process" ? PROCESS_REQUEST_Y : STRUCTURE_MAIN_Y
59
+ }
60
+ };
61
+ }
62
+ const nextSecondaryIndex = secondaryIndex++;
63
+ return {
64
+ ...node,
65
+ position: {
66
+ x: Math.max(1, Math.floor(nextSecondaryIndex / 2) + 1) * BRANCH_X_SPACING,
67
+ y: (options.variant === "process" ? PROCESS_BRANCH_Y : STRUCTURE_SECONDARY_Y) +
68
+ (nextSecondaryIndex % 2) * 116
69
+ }
70
+ };
71
+ }),
72
+ edges: model.edges
73
+ };
74
+ }
package/dist/index.d.ts CHANGED
@@ -26,4 +26,3 @@ export { buildReactFlowModel } from "./flow/build-react-flow-model.js";
26
26
  export { mockScanResult } from "./mock-data.js";
27
27
  export { buildReplaySteps } from "./replay/build-replay-steps.js";
28
28
  export { useReplayLite } from "./replay/use-replay-lite.js";
29
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -19,4 +19,3 @@ export { buildReactFlowModel } from "./flow/build-react-flow-model.js";
19
19
  export { mockScanResult } from "./mock-data.js";
20
20
  export { buildReplaySteps } from "./replay/build-replay-steps.js";
21
21
  export { useReplayLite } from "./replay/use-replay-lite.js";
22
- //# sourceMappingURL=index.js.map
@@ -1,3 +1,2 @@
1
1
  import { type ScanResult } from "@anlyx/core";
2
2
  export declare const mockScanResult: ScanResult;
3
- //# sourceMappingURL=mock-data.d.ts.map
package/dist/mock-data.js CHANGED
@@ -219,4 +219,3 @@ export const mockScanResult = scanResultSchema.parse({
219
219
  }
220
220
  ]
221
221
  });
222
- //# sourceMappingURL=mock-data.js.map
@@ -7,4 +7,3 @@ export type ReplayStep = {
7
7
  index: number;
8
8
  };
9
9
  export declare function buildReplaySteps(mainPath: string[]): ReplayStep[];
10
- //# sourceMappingURL=build-replay-steps.d.ts.map
@@ -23,4 +23,3 @@ export function buildReplaySteps(mainPath) {
23
23
  });
24
24
  return [...requestSteps, ...responseSteps];
25
25
  }
26
- //# sourceMappingURL=build-replay-steps.js.map
@@ -1,4 +1,4 @@
1
- import { type ReplayPhase } from "./build-replay-steps.js";
1
+ import { type ReplayPhase, type ReplayStep } from "./build-replay-steps.js";
2
2
  export type ReplayLiteState = {
3
3
  phase: ReplayPhase;
4
4
  isPlaying: boolean;
@@ -16,6 +16,7 @@ export type UseReplayLiteOptions = {
16
16
  };
17
17
  export type UseReplayLiteResult = {
18
18
  state: ReplayLiteState;
19
+ steps: ReplayStep[];
19
20
  loop: boolean;
20
21
  play: () => void;
21
22
  pause: () => void;
@@ -23,4 +24,3 @@ export type UseReplayLiteResult = {
23
24
  toggleLoop: () => void;
24
25
  };
25
26
  export declare function useReplayLite({ mainPath, loop: initialLoop, intervalMs }: UseReplayLiteOptions): UseReplayLiteResult;
26
- //# sourceMappingURL=use-replay-lite.d.ts.map
@@ -67,6 +67,7 @@ export function useReplayLite({ mainPath, loop: initialLoop = false, intervalMs
67
67
  isPlaying,
68
68
  phase
69
69
  }),
70
+ steps,
70
71
  loop,
71
72
  play,
72
73
  pause,
@@ -94,4 +95,3 @@ function toReplayState(options) {
94
95
  : {})
95
96
  };
96
97
  }
97
- //# sourceMappingURL=use-replay-lite.js.map