@fragments-sdk/cli 0.10.0 → 0.11.1

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 (167) hide show
  1. package/dist/bin.js +26 -8
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ZDA3PLQ6.js → chunk-5G3VZH43.js} +2 -2
  4. package/dist/{chunk-566BNPQZ.js → chunk-HRFUSSZI.js} +25 -6
  5. package/dist/chunk-HRFUSSZI.js.map +1 -0
  6. package/dist/{chunk-CAMXG5HJ.js → chunk-ZM4ZQZWZ.js} +2 -2
  7. package/dist/{generate-BGKTKO6E.js → generate-FBHSXR3D.js} +2 -2
  8. package/dist/index.js +2 -2
  9. package/dist/{init-Q53R5Q2T.js → init-UFGK5TCN.js} +77 -6
  10. package/dist/init-UFGK5TCN.js.map +1 -0
  11. package/dist/{scan-OQU7M4GH.js → scan-CJF2DOQW.js} +3 -3
  12. package/dist/{scan-generate-T5QNUG7N.js → scan-generate-SJAN5MVI.js} +2 -2
  13. package/dist/snapshot-SV2JOFZH.js +139 -0
  14. package/dist/snapshot-SV2JOFZH.js.map +1 -0
  15. package/dist/{test-2CSOSS3B.js → test-Z5LVO724.js} +2 -2
  16. package/dist/{tokens-DXEGYTOJ.js → tokens-CE46OTMD.js} +2 -2
  17. package/dist/{viewer-DBEPYM3G.js → viewer-DLLJIMCK.js} +69 -47
  18. package/dist/viewer-DLLJIMCK.js.map +1 -0
  19. package/package.json +6 -14
  20. package/src/bin.ts +30 -0
  21. package/src/commands/init.ts +76 -1
  22. package/src/commands/snapshot.ts +197 -0
  23. package/src/core/loader.ts +38 -8
  24. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  25. package/src/viewer/server.ts +37 -22
  26. package/src/viewer/vite-plugin.ts +25 -9
  27. package/dist/chunk-566BNPQZ.js.map +0 -1
  28. package/dist/init-Q53R5Q2T.js.map +0 -1
  29. package/dist/viewer-DBEPYM3G.js.map +0 -1
  30. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  31. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  32. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  33. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  34. package/src/viewer/assets/fragments-logo.ts +0 -4
  35. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  36. package/src/viewer/components/ActionCapture.tsx +0 -172
  37. package/src/viewer/components/ActionsPanel.tsx +0 -332
  38. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  39. package/src/viewer/components/App.tsx +0 -582
  40. package/src/viewer/components/BottomPanel.tsx +0 -288
  41. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  42. package/src/viewer/components/CodePanel.tsx +0 -118
  43. package/src/viewer/components/CommandPalette.tsx +0 -392
  44. package/src/viewer/components/ComponentDocView.tsx +0 -164
  45. package/src/viewer/components/ComponentGraph.tsx +0 -380
  46. package/src/viewer/components/ComponentHeader.tsx +0 -88
  47. package/src/viewer/components/ContractPanel.tsx +0 -241
  48. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  49. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  50. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  51. package/src/viewer/components/FragmentEditor.tsx +0 -525
  52. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  53. package/src/viewer/components/HeaderSearch.tsx +0 -24
  54. package/src/viewer/components/HealthDashboard.tsx +0 -441
  55. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  56. package/src/viewer/components/Icons.tsx +0 -479
  57. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  58. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  59. package/src/viewer/components/IsolatedRender.tsx +0 -113
  60. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  61. package/src/viewer/components/LandingPage.tsx +0 -421
  62. package/src/viewer/components/Layout.tsx +0 -27
  63. package/src/viewer/components/LeftSidebar.tsx +0 -472
  64. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  65. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  66. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  67. package/src/viewer/components/PanelShell.tsx +0 -161
  68. package/src/viewer/components/PerformancePanel.tsx +0 -304
  69. package/src/viewer/components/PreviewArea.tsx +0 -472
  70. package/src/viewer/components/PreviewAside.tsx +0 -168
  71. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  72. package/src/viewer/components/PreviewPane.tsx +0 -149
  73. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  74. package/src/viewer/components/PropsEditor.tsx +0 -506
  75. package/src/viewer/components/PropsTable.tsx +0 -111
  76. package/src/viewer/components/RelationsSection.tsx +0 -88
  77. package/src/viewer/components/ResizablePanel.tsx +0 -271
  78. package/src/viewer/components/RightSidebar.tsx +0 -102
  79. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  80. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  81. package/src/viewer/components/Sidebar.tsx +0 -169
  82. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  83. package/src/viewer/components/ThemeProvider.tsx +0 -42
  84. package/src/viewer/components/Toast.tsx +0 -3
  85. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  86. package/src/viewer/components/TopToolbar.tsx +0 -159
  87. package/src/viewer/components/UsageSection.tsx +0 -95
  88. package/src/viewer/components/VariantMatrix.tsx +0 -388
  89. package/src/viewer/components/VariantRenderer.tsx +0 -131
  90. package/src/viewer/components/VariantTabs.tsx +0 -40
  91. package/src/viewer/components/ViewerHeader.tsx +0 -69
  92. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  93. package/src/viewer/components/ViewportSelector.tsx +0 -172
  94. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  95. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  96. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  97. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  98. package/src/viewer/components/viewer-utils.ts +0 -16
  99. package/src/viewer/composition-renderer.ts +0 -381
  100. package/src/viewer/constants/index.ts +0 -1
  101. package/src/viewer/constants/ui.ts +0 -166
  102. package/src/viewer/entry.tsx +0 -335
  103. package/src/viewer/hooks/index.ts +0 -2
  104. package/src/viewer/hooks/useA11yCache.ts +0 -383
  105. package/src/viewer/hooks/useA11yService.ts +0 -364
  106. package/src/viewer/hooks/useActions.ts +0 -138
  107. package/src/viewer/hooks/useAppState.ts +0 -147
  108. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  109. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  110. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  111. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  112. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  113. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  114. package/src/viewer/hooks/useUrlState.ts +0 -318
  115. package/src/viewer/hooks/useViewSettings.ts +0 -111
  116. package/src/viewer/index.html +0 -28
  117. package/src/viewer/intelligence/healthReport.ts +0 -505
  118. package/src/viewer/intelligence/styleDrift.ts +0 -340
  119. package/src/viewer/intelligence/usageScanner.ts +0 -309
  120. package/src/viewer/jsx-parser.ts +0 -486
  121. package/src/viewer/preview-frame-entry.tsx +0 -25
  122. package/src/viewer/preview-frame.html +0 -125
  123. package/src/viewer/public/favicon.ico +0 -0
  124. package/src/viewer/render-template.html +0 -68
  125. package/src/viewer/styles/globals.css +0 -278
  126. package/src/viewer/types/a11y.ts +0 -197
  127. package/src/viewer/utils/a11y-fixes.ts +0 -509
  128. package/src/viewer/utils/actionExport.ts +0 -372
  129. package/src/viewer/utils/colorSchemes.ts +0 -201
  130. package/src/viewer/utils/detectRelationships.ts +0 -256
  131. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  132. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  133. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  134. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  135. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  136. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  137. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  138. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  139. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  140. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  141. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  142. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  143. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  144. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -134
  145. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  146. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  147. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  148. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  149. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  150. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  151. package/src/viewer/vendor/shared/src/index.ts +0 -34
  152. package/src/viewer/vendor/shared/src/types.ts +0 -53
  153. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  154. package/src/viewer/webmcp/analytics.ts +0 -165
  155. package/src/viewer/webmcp/index.ts +0 -3
  156. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  157. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  158. package/src/viewer/webmcp/scan-utils.ts +0 -135
  159. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  160. package/src/viewer/webmcp/viewer-state.ts +0 -45
  161. /package/dist/{chunk-ZDA3PLQ6.js.map → chunk-5G3VZH43.js.map} +0 -0
  162. /package/dist/{chunk-CAMXG5HJ.js.map → chunk-ZM4ZQZWZ.js.map} +0 -0
  163. /package/dist/{generate-BGKTKO6E.js.map → generate-FBHSXR3D.js.map} +0 -0
  164. /package/dist/{scan-OQU7M4GH.js.map → scan-CJF2DOQW.js.map} +0 -0
  165. /package/dist/{scan-generate-T5QNUG7N.js.map → scan-generate-SJAN5MVI.js.map} +0 -0
  166. /package/dist/{test-2CSOSS3B.js.map → test-Z5LVO724.js.map} +0 -0
  167. /package/dist/{tokens-DXEGYTOJ.js.map → tokens-CE46OTMD.js.map} +0 -0
@@ -1,757 +0,0 @@
1
- /**
2
- * Interactions Panel - Storybook-style interaction testing
3
- *
4
- * Runs play functions from Storybook stories and displays:
5
- * - Step-by-step progress
6
- * - Assertion results (pass/fail)
7
- * - Error messages with stack traces
8
- * - Re-run capabilities
9
- * - Step-through debugging with breakpoints
10
- */
11
-
12
- import { useState, useCallback, useRef, useEffect } from "react";
13
- import { Button, Stack, Text, Badge, CodeBlock, Alert, Box } from "@fragments-sdk/ui";
14
- import type { PlayFunction, PlayFunctionContext, FragmentVariant } from "../../core/index.js";
15
- import { Play } from "@phosphor-icons/react";
16
- import {
17
- PlayIcon,
18
- CheckIcon,
19
- XIcon,
20
- LoadingIcon,
21
- ChevronDownIcon,
22
- ChevronRightIcon,
23
- RefreshIcon,
24
- BugIcon,
25
- PauseIcon,
26
- StepOverIcon,
27
- ContinueIcon,
28
- BreakpointIcon,
29
- BreakpointEmptyIcon,
30
- } from "./Icons.js";
31
- import { PanelShell } from "./PanelShell.js";
32
-
33
- // Step execution state
34
- interface StepResult {
35
- name: string;
36
- status: "pending" | "running" | "passed" | "failed" | "paused";
37
- error?: string;
38
- duration?: number;
39
- }
40
-
41
- interface InteractionResult {
42
- status: "idle" | "running" | "passed" | "failed" | "paused";
43
- steps: StepResult[];
44
- error?: string;
45
- duration?: number;
46
- startTime?: number;
47
- }
48
-
49
- // Debug state
50
- interface DebugState {
51
- mode: "normal" | "debug";
52
- isPaused: boolean;
53
- currentStepIndex: number;
54
- breakpoints: Set<number>;
55
- }
56
-
57
- interface InteractionsPanelProps {
58
- /** The current variant being displayed */
59
- variant: FragmentVariant | null;
60
- /** Selector for the preview container element */
61
- previewSelector?: string;
62
- /** Key that changes when the preview updates */
63
- previewKey?: number;
64
- /** Args currently being used for the preview */
65
- currentArgs?: Record<string, unknown>;
66
- }
67
-
68
- // Status color helpers
69
- const STATUS_COLORS: Record<string, { bg: string; border: string; text: string; icon: string }> = {
70
- running: {
71
- bg: 'color-mix(in srgb, #3b82f6 8%, transparent)',
72
- border: '1px solid color-mix(in srgb, #3b82f6 25%, transparent)',
73
- text: '#1d4ed8',
74
- icon: '#2563eb',
75
- },
76
- paused: {
77
- bg: 'color-mix(in srgb, #f97316 8%, transparent)',
78
- border: '1px solid color-mix(in srgb, #f97316 25%, transparent)',
79
- text: '#c2410c',
80
- icon: '#ea580c',
81
- },
82
- passed: {
83
- bg: 'color-mix(in srgb, #22c55e 8%, transparent)',
84
- border: '1px solid color-mix(in srgb, #22c55e 25%, transparent)',
85
- text: '#15803d',
86
- icon: '#16a34a',
87
- },
88
- failed: {
89
- bg: 'color-mix(in srgb, #ef4444 8%, transparent)',
90
- border: '1px solid color-mix(in srgb, #ef4444 25%, transparent)',
91
- text: '#b91c1c',
92
- icon: '#dc2626',
93
- },
94
- pending: {
95
- bg: 'var(--bg-secondary)',
96
- border: '1px solid var(--border)',
97
- text: 'var(--text-muted)',
98
- icon: 'var(--text-muted)',
99
- },
100
- };
101
-
102
- function getStatusColors(status: string) {
103
- return STATUS_COLORS[status] || STATUS_COLORS.pending;
104
- }
105
-
106
- export function InteractionsPanel({
107
- variant,
108
- previewSelector = '[data-preview-container="true"]',
109
- previewKey = 0,
110
- currentArgs = {},
111
- }: InteractionsPanelProps) {
112
- const [result, setResult] = useState<InteractionResult>({
113
- status: "idle",
114
- steps: [],
115
- });
116
- const [expandedSteps, setExpandedSteps] = useState<Set<number>>(new Set());
117
- const [debugState, setDebugState] = useState<DebugState>({
118
- mode: "normal",
119
- isPaused: false,
120
- currentStepIndex: -1,
121
- breakpoints: new Set(),
122
- });
123
- const [hoveredBreakpoint, setHoveredBreakpoint] = useState<number | null>(null);
124
- const abortControllerRef = useRef<AbortController | null>(null);
125
- const resumeResolverRef = useRef<(() => void) | null>(null);
126
-
127
- // Reset state when variant changes
128
- useEffect(() => {
129
- setResult({ status: "idle", steps: [] });
130
- setExpandedSteps(new Set());
131
- setDebugState((prev) => ({
132
- ...prev,
133
- isPaused: false,
134
- currentStepIndex: -1,
135
- }));
136
- }, [variant?.name, previewKey]);
137
-
138
- // Keyboard shortcuts
139
- useEffect(() => {
140
- const handleKeyDown = (e: KeyboardEvent) => {
141
- // Only handle if this panel is focused or no specific element is focused
142
- if (document.activeElement && document.activeElement.tagName !== 'BODY') {
143
- const isInteractionsPanelFocused = (document.activeElement as HTMLElement).closest('[data-interactions-panel]');
144
- if (!isInteractionsPanelFocused) return;
145
- }
146
-
147
- switch (e.key) {
148
- case 'F5':
149
- e.preventDefault();
150
- if (debugState.mode === 'debug' && !result.status.includes('running')) {
151
- runInteractions(true);
152
- }
153
- break;
154
- case 'F8':
155
- e.preventDefault();
156
- if (debugState.isPaused) {
157
- handleResume();
158
- }
159
- break;
160
- case 'F9':
161
- e.preventDefault();
162
- if (debugState.currentStepIndex >= 0) {
163
- toggleBreakpoint(debugState.currentStepIndex);
164
- }
165
- break;
166
- case 'F10':
167
- e.preventDefault();
168
- if (debugState.isPaused) {
169
- handleStepOver();
170
- }
171
- break;
172
- }
173
- };
174
-
175
- window.addEventListener('keydown', handleKeyDown);
176
- return () => window.removeEventListener('keydown', handleKeyDown);
177
- }, [debugState, result.status]);
178
-
179
- const hasPlayFunction = variant?.hasPlayFunction && variant?.play;
180
-
181
- const toggleBreakpoint = (index: number) => {
182
- setDebugState((prev) => {
183
- const next = new Set(prev.breakpoints);
184
- if (next.has(index)) {
185
- next.delete(index);
186
- } else {
187
- next.add(index);
188
- }
189
- return { ...prev, breakpoints: next };
190
- });
191
- };
192
-
193
- const toggleDebugMode = () => {
194
- setDebugState((prev) => ({
195
- ...prev,
196
- mode: prev.mode === 'normal' ? 'debug' : 'normal',
197
- }));
198
- };
199
-
200
- const handleResume = () => {
201
- if (resumeResolverRef.current) {
202
- resumeResolverRef.current();
203
- resumeResolverRef.current = null;
204
- }
205
- setDebugState((prev) => ({ ...prev, isPaused: false }));
206
- setResult((prev) => ({ ...prev, status: 'running' }));
207
- };
208
-
209
- const handleStepOver = () => {
210
- // Mark current step to not pause, then resume
211
- if (resumeResolverRef.current) {
212
- resumeResolverRef.current();
213
- resumeResolverRef.current = null;
214
- }
215
- setDebugState((prev) => ({ ...prev, isPaused: false }));
216
- setResult((prev) => ({ ...prev, status: 'running' }));
217
- };
218
-
219
- // Run the play function
220
- const runInteractions = useCallback(async (withDebugger = false) => {
221
- if (!variant?.play) return;
222
-
223
- // Cancel any existing run
224
- if (abortControllerRef.current) {
225
- abortControllerRef.current.abort();
226
- }
227
- abortControllerRef.current = new AbortController();
228
-
229
- const startTime = performance.now();
230
- const steps: StepResult[] = [];
231
- const isDebugMode = withDebugger || debugState.mode === 'debug';
232
- let stepIndex = 0;
233
-
234
- setResult({
235
- status: "running",
236
- steps: [],
237
- startTime,
238
- });
239
-
240
- setDebugState((prev) => ({
241
- ...prev,
242
- isPaused: false,
243
- currentStepIndex: -1,
244
- }));
245
-
246
- // Find the preview container
247
- const canvasElement = document.querySelector(previewSelector) as HTMLElement;
248
- if (!canvasElement) {
249
- setResult({
250
- status: "failed",
251
- steps: [],
252
- error: `Preview container not found: ${previewSelector}`,
253
- duration: performance.now() - startTime,
254
- });
255
- return;
256
- }
257
-
258
- // Create the step function that tracks execution
259
- const step = async (name: string, fn: () => Promise<void>): Promise<void> => {
260
- const currentIndex = stepIndex++;
261
- const stepStartTime = performance.now();
262
-
263
- // Check for breakpoint or debug mode pause
264
- if (isDebugMode && (debugState.breakpoints.has(currentIndex) || currentIndex === 0)) {
265
- // Pause at breakpoint
266
- steps.push({ name, status: "paused" });
267
- setResult((prev) => ({
268
- ...prev,
269
- status: "paused",
270
- steps: [...steps],
271
- }));
272
- setDebugState((prev) => ({
273
- ...prev,
274
- isPaused: true,
275
- currentStepIndex: currentIndex,
276
- }));
277
-
278
- // Wait for resume
279
- await new Promise<void>((resolve) => {
280
- resumeResolverRef.current = resolve;
281
- });
282
-
283
- // Update to running
284
- steps[currentIndex] = { name, status: "running" };
285
- setResult((prev) => ({
286
- ...prev,
287
- status: "running",
288
- steps: [...steps],
289
- }));
290
- } else {
291
- // Add step as running
292
- steps.push({ name, status: "running" });
293
- setResult((prev) => ({
294
- ...prev,
295
- steps: [...steps],
296
- }));
297
- }
298
-
299
- setDebugState((prev) => ({
300
- ...prev,
301
- currentStepIndex: currentIndex,
302
- }));
303
-
304
- try {
305
- await fn();
306
- steps[currentIndex] = {
307
- name,
308
- status: "passed",
309
- duration: performance.now() - stepStartTime,
310
- };
311
- setResult((prev) => ({
312
- ...prev,
313
- steps: [...steps],
314
- }));
315
- } catch (error) {
316
- const errorMessage = error instanceof Error ? error.message : String(error);
317
- steps[currentIndex] = {
318
- name,
319
- status: "failed",
320
- error: errorMessage,
321
- duration: performance.now() - stepStartTime,
322
- };
323
- setResult((prev) => ({
324
- ...prev,
325
- steps: [...steps],
326
- }));
327
- throw error; // Re-throw to stop execution
328
- }
329
-
330
- // In debug mode, pause after each step if stepping
331
- if (isDebugMode && debugState.breakpoints.has(currentIndex + 1)) {
332
- setResult((prev) => ({
333
- ...prev,
334
- status: "paused",
335
- }));
336
- setDebugState((prev) => ({
337
- ...prev,
338
- isPaused: true,
339
- }));
340
-
341
- await new Promise<void>((resolve) => {
342
- resumeResolverRef.current = resolve;
343
- });
344
- }
345
- };
346
-
347
- // Create the context for the play function
348
- const context: PlayFunctionContext = {
349
- canvasElement,
350
- args: currentArgs,
351
- step,
352
- };
353
-
354
- try {
355
- await variant.play(context);
356
-
357
- // If no explicit steps were defined, the whole play function is one step
358
- if (steps.length === 0) {
359
- steps.push({
360
- name: "Play function",
361
- status: "passed",
362
- duration: performance.now() - startTime,
363
- });
364
- }
365
-
366
- setResult({
367
- status: "passed",
368
- steps: [...steps],
369
- duration: performance.now() - startTime,
370
- });
371
-
372
- setDebugState((prev) => ({
373
- ...prev,
374
- isPaused: false,
375
- currentStepIndex: -1,
376
- }));
377
- } catch (error) {
378
- const errorMessage = error instanceof Error ? error.message : String(error);
379
-
380
- // If no steps failed explicitly, mark as failed at top level
381
- if (steps.length === 0 || !steps.some((s) => s.status === "failed")) {
382
- steps.push({
383
- name: "Play function",
384
- status: "failed",
385
- error: errorMessage,
386
- duration: performance.now() - startTime,
387
- });
388
- }
389
-
390
- setResult({
391
- status: "failed",
392
- steps: [...steps],
393
- error: errorMessage,
394
- duration: performance.now() - startTime,
395
- });
396
-
397
- // Auto-expand failed steps
398
- const failedIndices = steps
399
- .map((s, i) => (s.status === "failed" ? i : -1))
400
- .filter((i) => i >= 0);
401
- setExpandedSteps(new Set(failedIndices));
402
-
403
- setDebugState((prev) => ({
404
- ...prev,
405
- isPaused: false,
406
- currentStepIndex: -1,
407
- }));
408
- }
409
- }, [variant, previewSelector, currentArgs, debugState.mode, debugState.breakpoints]);
410
-
411
- const toggleStep = (index: number) => {
412
- setExpandedSteps((prev) => {
413
- const next = new Set(prev);
414
- if (next.has(index)) {
415
- next.delete(index);
416
- } else {
417
- next.add(index);
418
- }
419
- return next;
420
- });
421
- };
422
-
423
- const formatDuration = (ms?: number) => {
424
- if (ms === undefined) return "";
425
- if (ms < 1000) return `${Math.round(ms)}ms`;
426
- return `${(ms / 1000).toFixed(2)}s`;
427
- };
428
-
429
- // Empty state for no play function
430
- const emptyConfig = !hasPlayFunction ? {
431
- icon: <Play size={24} weight="regular" style={{ color: 'var(--text-tertiary)' }} />,
432
- title: "No interactions defined",
433
- description: (
434
- <>
435
- This variant doesn't have a play function. Add a <Box as="code" padding="xs" background="secondary" rounded="sm" style={{ fontSize: '12px', display: 'inline' }}>play</Box> function to your Storybook story to enable interaction testing.
436
- </>
437
- ),
438
- action: (
439
- <CodeBlock language="typescript">{`export const Default = {
440
- play: async ({ canvasElement, step }) => {
441
- const canvas = within(canvasElement);
442
-
443
- await step('Click the button', async () => {
444
- await userEvent.click(
445
- canvas.getByRole('button')
446
- );
447
- });
448
-
449
- await expect(
450
- canvas.getByText('Clicked!')
451
- ).toBeInTheDocument();
452
- }
453
- };`}</CodeBlock>
454
- ),
455
- } : undefined;
456
-
457
- // Toolbar with duration, debug controls, and run button
458
- const toolbar = hasPlayFunction ? (
459
- <Stack direction="row" align="center" justify="between" style={{ width: '100%' }}>
460
- <Stack direction="row" align="center" gap="sm">
461
- {result.duration !== undefined && (
462
- <Text size="xs" color="tertiary">{formatDuration(result.duration)}</Text>
463
- )}
464
- </Stack>
465
- <Stack direction="row" align="center" gap="sm">
466
- {/* Debug mode toggle */}
467
- <Button
468
- onClick={toggleDebugMode}
469
- variant="ghost"
470
- size="sm"
471
- title={debugState.mode === 'debug' ? "Exit debug mode" : "Enable debug mode (F5 to run with debugger)"}
472
- style={{
473
- color: debugState.mode === 'debug' ? '#ea580c' : undefined,
474
- background: debugState.mode === 'debug' ? 'color-mix(in srgb, #f97316 10%, transparent)' : undefined,
475
- }}
476
- >
477
- <BugIcon style={{ width: 16, height: 16 }} />
478
- </Button>
479
-
480
- {/* Debug controls when paused */}
481
- {debugState.isPaused && (
482
- <>
483
- <Button
484
- onClick={handleResume}
485
- variant="ghost"
486
- size="sm"
487
- title="Continue (F8)"
488
- style={{ color: '#16a34a' }}
489
- >
490
- <ContinueIcon style={{ width: 16, height: 16 }} />
491
- </Button>
492
- <Button
493
- onClick={handleStepOver}
494
- variant="ghost"
495
- size="sm"
496
- title="Step over (F10)"
497
- style={{ color: '#2563eb' }}
498
- >
499
- <StepOverIcon style={{ width: 16, height: 16 }} />
500
- </Button>
501
- </>
502
- )}
503
-
504
- <Button
505
- onClick={() => runInteractions(debugState.mode === 'debug')}
506
- disabled={result.status === "running"}
507
- variant={result.status === "running" || result.status === "paused" ? "outline" : "solid"}
508
- size="sm"
509
- style={
510
- result.status === "running" || result.status === "paused"
511
- ? { background: 'var(--bg-secondary)', color: 'var(--text-muted)', cursor: 'not-allowed' }
512
- : { background: 'var(--color-accent)', color: '#fff' }
513
- }
514
- >
515
- {result.status === "running" ? (
516
- <>
517
- <LoadingIcon style={{ width: 16, height: 16, animation: 'spin 1s linear infinite' }} />
518
- Running...
519
- </>
520
- ) : result.status === "paused" ? (
521
- <>
522
- <PauseIcon style={{ width: 16, height: 16 }} />
523
- Paused
524
- </>
525
- ) : result.status === "idle" ? (
526
- <>
527
- <PlayIcon style={{ width: 16, height: 16 }} />
528
- {debugState.mode === 'debug' ? 'Debug' : 'Run'}
529
- </>
530
- ) : (
531
- <>
532
- <RefreshIcon style={{ width: 16, height: 16 }} />
533
- Rerun
534
- </>
535
- )}
536
- </Button>
537
- </Stack>
538
- </Stack>
539
- ) : undefined;
540
-
541
- return (
542
- <div data-interactions-panel style={{ height: '100%' }}>
543
- <PanelShell toolbar={toolbar} empty={emptyConfig} bodyPadding="none">
544
- {result.status === "idle" ? (
545
- <Box padding="lg" style={{ textAlign: 'center' }}>
546
- <Text size="sm" color="tertiary">
547
- Click "Run" to execute the interaction tests
548
- </Text>
549
- </Box>
550
- ) : (
551
- <Stack direction="column" gap="sm" style={{ padding: '16px' }}>
552
- {/* Overall status */}
553
- {(() => {
554
- const colors = getStatusColors(result.status);
555
- const alertVariant = result.status === 'passed' ? 'success'
556
- : result.status === 'failed' ? 'error'
557
- : result.status === 'paused' ? 'warning'
558
- : 'info';
559
- return (
560
- <Alert variant={alertVariant}>
561
- <Alert.Body>
562
- <Stack direction="row" align="center" justify="between" style={{ width: '100%' }}>
563
- <Stack direction="row" align="center" gap="sm">
564
- {result.status === "running" && (
565
- <LoadingIcon style={{ width: 20, height: 20, animation: 'spin 1s linear infinite', color: colors.icon }} />
566
- )}
567
- {result.status === "paused" && (
568
- <PauseIcon style={{ width: 20, height: 20, color: colors.icon }} />
569
- )}
570
- {result.status === "passed" && (
571
- <CheckIcon style={{ width: 20, height: 20, color: colors.icon }} />
572
- )}
573
- {result.status === "failed" && (
574
- <XIcon style={{ width: 20, height: 20, color: colors.icon }} />
575
- )}
576
- <Text weight="medium" style={{ color: colors.text }}>
577
- {result.status === "running" && "Running interactions..."}
578
- {result.status === "paused" && "Paused at breakpoint"}
579
- {result.status === "passed" && "All interactions passed"}
580
- {result.status === "failed" && "Interactions failed"}
581
- </Text>
582
- </Stack>
583
- <Text size="xs" color="tertiary">
584
- {result.steps.filter((s) => s.status === "passed").length}/{result.steps.length} steps
585
- </Text>
586
- </Stack>
587
- </Alert.Body>
588
- </Alert>
589
- );
590
- })()}
591
-
592
- {/* Steps */}
593
- {result.steps.length > 0 && (
594
- <Stack direction="column" gap="xs" style={{ marginTop: '16px' }}>
595
- <Stack direction="row" align="center" justify="between" style={{ marginBottom: '8px' }}>
596
- <Text size="xs" weight="medium" color="tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
597
- Steps
598
- </Text>
599
- {debugState.mode === 'debug' && (
600
- <Text size="xs" style={{ color: '#ea580c' }}>
601
- Debug mode - click gutter to set breakpoints
602
- </Text>
603
- )}
604
- </Stack>
605
- {result.steps.map((step, index) => {
606
- const stepColors = getStatusColors(step.status);
607
- return (
608
- <div
609
- key={index}
610
- style={{
611
- borderRadius: '8px',
612
- border: step.status === 'passed' ? '1px solid var(--border)' : stepColors.border,
613
- transition: 'background 0.15s',
614
- display: 'flex',
615
- background: step.status === 'passed' ? 'var(--bg-primary)' : stepColors.bg,
616
- outline: debugState.currentStepIndex === index ? '2px solid #f97316' : undefined,
617
- outlineOffset: debugState.currentStepIndex === index ? '0px' : undefined,
618
- }}
619
- >
620
- {/* Breakpoint gutter - only show in debug mode */}
621
- {debugState.mode === 'debug' && (
622
- <button
623
- onClick={(e) => {
624
- e.stopPropagation();
625
- toggleBreakpoint(index);
626
- }}
627
- style={{
628
- width: '24px',
629
- flexShrink: 0,
630
- display: 'flex',
631
- alignItems: 'center',
632
- justifyContent: 'center',
633
- border: 'none',
634
- borderRight: '1px solid var(--border)',
635
- background: hoveredBreakpoint === index ? 'var(--bg-hover)' : 'transparent',
636
- transition: 'background 0.15s',
637
- cursor: 'pointer',
638
- padding: 0,
639
- }}
640
- onMouseEnter={() => setHoveredBreakpoint(index)}
641
- onMouseLeave={() => setHoveredBreakpoint(null)}
642
- title={debugState.breakpoints.has(index) ? "Remove breakpoint (F9)" : "Add breakpoint (F9)"}
643
- >
644
- {debugState.breakpoints.has(index) ? (
645
- <BreakpointIcon style={{ width: 12, height: 12, color: '#ef4444' }} />
646
- ) : (
647
- <BreakpointEmptyIcon style={{ width: 12, height: 12, color: hoveredBreakpoint === index ? '#f87171' : 'var(--text-muted)' }} />
648
- )}
649
- </button>
650
- )}
651
-
652
- <div style={{ flex: 1 }}>
653
- <button
654
- onClick={() => step.error && toggleStep(index)}
655
- style={{
656
- width: '100%',
657
- padding: '12px',
658
- display: 'flex',
659
- alignItems: 'center',
660
- gap: '8px',
661
- textAlign: 'left',
662
- cursor: step.error ? 'pointer' : 'default',
663
- border: 'none',
664
- background: 'transparent',
665
- color: 'inherit',
666
- }}
667
- disabled={!step.error}
668
- >
669
- {/* Current step indicator */}
670
- {debugState.mode === 'debug' && debugState.currentStepIndex === index && (
671
- <span style={{ color: '#f97316', fontWeight: 700 }}>&#9654;</span>
672
- )}
673
-
674
- {/* Status icon */}
675
- {step.status === "pending" && (
676
- <div style={{ width: '16px', height: '16px', borderRadius: '9999px', border: '2px solid var(--text-muted)' }} />
677
- )}
678
- {step.status === "running" && (
679
- <LoadingIcon style={{ width: 16, height: 16, animation: 'spin 1s linear infinite', color: stepColors.icon }} />
680
- )}
681
- {step.status === "paused" && (
682
- <PauseIcon style={{ width: 16, height: 16, color: stepColors.icon }} />
683
- )}
684
- {step.status === "passed" && (
685
- <CheckIcon style={{ width: 16, height: 16, color: stepColors.icon }} />
686
- )}
687
- {step.status === "failed" && (
688
- <XIcon style={{ width: 16, height: 16, color: stepColors.icon }} />
689
- )}
690
-
691
- {/* Step name */}
692
- <Text size="sm" style={{ flex: 1, color: stepColors.text }}>
693
- {step.name}
694
- </Text>
695
-
696
- {/* Duration */}
697
- {step.duration !== undefined && (
698
- <Text size="xs" color="tertiary">
699
- {formatDuration(step.duration)}
700
- </Text>
701
- )}
702
-
703
- {/* Expand icon for errors */}
704
- {step.error && (
705
- expandedSteps.has(index) ? (
706
- <ChevronDownIcon style={{ width: 16, height: 16, color: 'var(--text-muted)' }} />
707
- ) : (
708
- <ChevronRightIcon style={{ width: 16, height: 16, color: 'var(--text-muted)' }} />
709
- )
710
- )}
711
- </button>
712
-
713
- {/* Error details */}
714
- {step.error && expandedSteps.has(index) && (
715
- <div style={{ padding: '0 12px 12px 36px' }}>
716
- <pre style={{ fontSize: '12px', color: '#dc2626', background: 'color-mix(in srgb, #ef4444 10%, transparent)', padding: '8px', borderRadius: '4px', overflowX: 'auto', whiteSpace: 'pre-wrap' }}>
717
- {step.error}
718
- </pre>
719
- </div>
720
- )}
721
- </div>
722
- </div>
723
- );
724
- })}
725
- </Stack>
726
- )}
727
-
728
- {/* Keyboard shortcuts help in debug mode */}
729
- {debugState.mode === 'debug' && (
730
- <Box padding="sm" background="secondary" rounded="lg" style={{ marginTop: '16px' }}>
731
- <Text size="xs" weight="medium" color="tertiary" style={{ marginBottom: '4px' }}>Keyboard shortcuts:</Text>
732
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px' }}>
733
- <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F5</Badge> Run with debugger</Text>
734
- <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F8</Badge> Continue</Text>
735
- <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F9</Badge> Toggle breakpoint</Text>
736
- <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F10</Badge> Step over</Text>
737
- </div>
738
- </Box>
739
- )}
740
-
741
- {/* Top-level error (if no steps failed) */}
742
- {result.error && !result.steps.some((s) => s.status === "failed") && (
743
- <Box style={{ marginTop: '16px' }}>
744
- <Text size="xs" weight="medium" color="tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '8px' }}>
745
- Error
746
- </Text>
747
- <pre style={{ fontSize: '12px', color: '#dc2626', background: 'color-mix(in srgb, #ef4444 10%, transparent)', padding: '12px', borderRadius: '4px', overflowX: 'auto', whiteSpace: 'pre-wrap' }}>
748
- {result.error}
749
- </pre>
750
- </Box>
751
- )}
752
- </Stack>
753
- )}
754
- </PanelShell>
755
- </div>
756
- );
757
- }