@fragments-sdk/cli 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/bin.js +996 -79
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-ICAIQ57V.js → chunk-6JBGU74P.js} +5 -3
  4. package/dist/chunk-6JBGU74P.js.map +1 -0
  5. package/dist/chunk-7OPWMLOE.js +1625 -0
  6. package/dist/chunk-7OPWMLOE.js.map +1 -0
  7. package/dist/{chunk-2H2JAA3U.js → chunk-CVXKXVOY.js} +3 -3
  8. package/dist/{chunk-2H2JAA3U.js.map → chunk-CVXKXVOY.js.map} +1 -1
  9. package/dist/{chunk-IOJE35DZ.js → chunk-NWQ4CJOQ.js} +3 -3
  10. package/dist/{chunk-2DJH4F4P.js → chunk-RVRTRESS.js} +3 -3
  11. package/dist/{chunk-V7YLRR4C.js → chunk-TJ34N7C7.js} +41 -4
  12. package/dist/{chunk-V7YLRR4C.js.map → chunk-TJ34N7C7.js.map} +1 -1
  13. package/dist/{chunk-XNWDI6UT.js → chunk-XHUDJNN3.js} +5 -5
  14. package/dist/{core-DKHB7FYV.js → core-W2HYIQW6.js} +4 -4
  15. package/dist/{generate-KL24VZVD.js → generate-LMTISDIJ.js} +5 -5
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +15 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/{init-NION5S3M.js → init-7CHRKQ7P.js} +5 -5
  20. package/dist/mcp-bin.js +8 -220
  21. package/dist/mcp-bin.js.map +1 -1
  22. package/dist/scan-WY23TJCP.js +12 -0
  23. package/dist/{service-RWUMZ3EW.js → service-T2L7VLTE.js} +5 -5
  24. package/dist/static-viewer-GBR7YNF3.js +12 -0
  25. package/dist/{test-ECPEXFDN.js → test-OJRXNDO2.js} +4 -4
  26. package/dist/{tokens-ITADYVPF.js → tokens-3BWDESVM.js} +6 -6
  27. package/dist/viewer-SUFOISZM.js +1822 -0
  28. package/dist/viewer-SUFOISZM.js.map +1 -0
  29. package/package.json +6 -5
  30. package/src/bin.ts +31 -0
  31. package/src/build.ts +147 -13
  32. package/src/cli-commands.ts +18 -0
  33. package/src/commands/__tests__/a11y-scoring.test.ts +278 -0
  34. package/src/commands/a11y-report.ts +625 -0
  35. package/src/commands/a11y.ts +168 -14
  36. package/src/commands/build.ts +16 -0
  37. package/src/commands/graph.ts +274 -0
  38. package/src/core/auto-props.ts +464 -0
  39. package/src/core/composition.ts +64 -1
  40. package/src/core/graph-extractor.test.ts +542 -0
  41. package/src/core/graph-extractor.ts +601 -0
  42. package/src/core/importAnalyzer.ts +5 -0
  43. package/src/core/schema.ts +2 -0
  44. package/src/core/types.ts +3 -1
  45. package/src/index.ts +4 -0
  46. package/src/mcp/server.ts +13 -220
  47. package/src/theme/__tests__/component-contrast.test.ts +338 -0
  48. package/src/theme/__tests__/contrast-validation.test.ts +326 -0
  49. package/src/theme/contrast.test.ts +331 -0
  50. package/src/theme/contrast.ts +246 -0
  51. package/src/theme/generator.ts +213 -1
  52. package/src/theme/index.ts +16 -0
  53. package/src/theme/types.ts +51 -0
  54. package/src/viewer/__tests__/a11y-fixes.test.ts +358 -0
  55. package/src/viewer/__tests__/viewer-integration.test.ts +2 -7
  56. package/src/viewer/components/AccessibilityPanel.tsx +493 -433
  57. package/src/viewer/components/ActionCapture.tsx +1 -1
  58. package/src/viewer/components/ActionsPanel.tsx +142 -183
  59. package/src/viewer/components/App.tsx +276 -183
  60. package/src/viewer/components/BottomPanel.tsx +40 -80
  61. package/src/viewer/components/CodePanel.tsx +9 -87
  62. package/src/viewer/components/CommandPalette.tsx +117 -74
  63. package/src/viewer/components/ComponentGraph.tsx +143 -126
  64. package/src/viewer/components/ComponentHeader.tsx +46 -43
  65. package/src/viewer/components/ContractPanel.tsx +124 -117
  66. package/src/viewer/components/ErrorBoundary.tsx +47 -35
  67. package/src/viewer/components/FigmaEmbed.tsx +18 -13
  68. package/src/viewer/components/FragmentEditor.tsx +126 -63
  69. package/src/viewer/components/HealthDashboard.tsx +146 -171
  70. package/src/viewer/components/HmrStatusIndicator.tsx +31 -41
  71. package/src/viewer/components/Icons.tsx +151 -98
  72. package/src/viewer/components/InteractionsPanel.tsx +317 -264
  73. package/src/viewer/components/IsolatedPreviewFrame.tsx +52 -27
  74. package/src/viewer/components/IsolatedRender.tsx +12 -6
  75. package/src/viewer/components/KeyboardShortcutsHelp.tsx +34 -70
  76. package/src/viewer/components/LandingPage.tsx +285 -305
  77. package/src/viewer/components/Layout.tsx +12 -10
  78. package/src/viewer/components/LeftSidebar.tsx +103 -155
  79. package/src/viewer/components/MultiViewportPreview.tsx +254 -63
  80. package/src/viewer/components/PreviewArea.tsx +113 -44
  81. package/src/viewer/components/PreviewFrameHost.tsx +36 -6
  82. package/src/viewer/components/PreviewPane.tsx +2 -3
  83. package/src/viewer/components/PreviewToolbar.tsx +109 -105
  84. package/src/viewer/components/PropsEditor.tsx +154 -74
  85. package/src/viewer/components/PropsTable.tsx +95 -82
  86. package/src/viewer/components/RelationsSection.tsx +71 -40
  87. package/src/viewer/components/ResizablePanel.tsx +158 -55
  88. package/src/viewer/components/RightSidebar.tsx +46 -56
  89. package/src/viewer/components/ScreenshotButton.tsx +12 -12
  90. package/src/viewer/components/SkeletonLoader.tsx +99 -83
  91. package/src/viewer/components/StoryRenderer.tsx +4 -11
  92. package/src/viewer/components/Toast.tsx +3 -67
  93. package/src/viewer/components/TokenStylePanel.tsx +136 -118
  94. package/src/viewer/components/UsageSection.tsx +26 -26
  95. package/src/viewer/components/VariantMatrix.tsx +140 -47
  96. package/src/viewer/components/VariantTabs.tsx +24 -68
  97. package/src/viewer/components/ViewportSelector.tsx +121 -114
  98. package/src/viewer/constants/ui.ts +23 -22
  99. package/src/viewer/entry.tsx +8 -3
  100. package/src/viewer/index.ts +3 -6
  101. package/src/viewer/preview-frame.html +43 -18
  102. package/src/viewer/server.ts +7 -16
  103. package/src/viewer/styles/globals.css +46 -85
  104. package/src/viewer/utils/a11y-fixes.ts +53 -30
  105. package/dist/chunk-ICAIQ57V.js.map +0 -1
  106. package/dist/chunk-U4GQ2JTD.js +0 -832
  107. package/dist/chunk-U4GQ2JTD.js.map +0 -1
  108. package/dist/scan-ESEXV7LF.js +0 -12
  109. package/dist/static-viewer-O37MJ5B6.js +0 -12
  110. package/dist/viewer-YDGFDTK5.js +0 -11104
  111. package/dist/viewer-YDGFDTK5.js.map +0 -1
  112. package/src/viewer/postcss.config.js +0 -6
  113. package/src/viewer/tailwind.config.js +0 -37
  114. /package/dist/{chunk-IOJE35DZ.js.map → chunk-NWQ4CJOQ.js.map} +0 -0
  115. /package/dist/{chunk-2DJH4F4P.js.map → chunk-RVRTRESS.js.map} +0 -0
  116. /package/dist/{chunk-XNWDI6UT.js.map → chunk-XHUDJNN3.js.map} +0 -0
  117. /package/dist/{core-DKHB7FYV.js.map → core-W2HYIQW6.js.map} +0 -0
  118. /package/dist/{generate-KL24VZVD.js.map → generate-LMTISDIJ.js.map} +0 -0
  119. /package/dist/{init-NION5S3M.js.map → init-7CHRKQ7P.js.map} +0 -0
  120. /package/dist/{scan-ESEXV7LF.js.map → scan-WY23TJCP.js.map} +0 -0
  121. /package/dist/{service-RWUMZ3EW.js.map → service-T2L7VLTE.js.map} +0 -0
  122. /package/dist/{static-viewer-O37MJ5B6.js.map → static-viewer-GBR7YNF3.js.map} +0 -0
  123. /package/dist/{test-ECPEXFDN.js.map → test-OJRXNDO2.js.map} +0 -0
  124. /package/dist/{tokens-ITADYVPF.js.map → tokens-3BWDESVM.js.map} +0 -0
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { useState, useCallback, useRef, useEffect } from "react";
13
- import clsx from "clsx";
13
+ import { Button, Stack, Text, Badge, EmptyState, CodeBlock, Alert } from "@fragments/ui";
14
14
  import type { PlayFunction, PlayFunctionContext, SegmentVariant } from "../../core/index.js";
15
15
  import {
16
16
  PlayIcon,
@@ -63,6 +63,44 @@ interface InteractionsPanelProps {
63
63
  currentArgs?: Record<string, unknown>;
64
64
  }
65
65
 
66
+ // Status color helpers
67
+ const STATUS_COLORS: Record<string, { bg: string; border: string; text: string; icon: string }> = {
68
+ running: {
69
+ bg: 'color-mix(in srgb, #3b82f6 8%, transparent)',
70
+ border: '1px solid color-mix(in srgb, #3b82f6 25%, transparent)',
71
+ text: '#1d4ed8',
72
+ icon: '#2563eb',
73
+ },
74
+ paused: {
75
+ bg: 'color-mix(in srgb, #f97316 8%, transparent)',
76
+ border: '1px solid color-mix(in srgb, #f97316 25%, transparent)',
77
+ text: '#c2410c',
78
+ icon: '#ea580c',
79
+ },
80
+ passed: {
81
+ bg: 'color-mix(in srgb, #22c55e 8%, transparent)',
82
+ border: '1px solid color-mix(in srgb, #22c55e 25%, transparent)',
83
+ text: '#15803d',
84
+ icon: '#16a34a',
85
+ },
86
+ failed: {
87
+ bg: 'color-mix(in srgb, #ef4444 8%, transparent)',
88
+ border: '1px solid color-mix(in srgb, #ef4444 25%, transparent)',
89
+ text: '#b91c1c',
90
+ icon: '#dc2626',
91
+ },
92
+ pending: {
93
+ bg: 'var(--bg-secondary)',
94
+ border: '1px solid var(--border)',
95
+ text: 'var(--text-muted)',
96
+ icon: 'var(--text-muted)',
97
+ },
98
+ };
99
+
100
+ function getStatusColors(status: string) {
101
+ return STATUS_COLORS[status] || STATUS_COLORS.pending;
102
+ }
103
+
66
104
  export function InteractionsPanel({
67
105
  variant,
68
106
  previewSelector = '[data-preview-container="true"]',
@@ -80,6 +118,7 @@ export function InteractionsPanel({
80
118
  currentStepIndex: -1,
81
119
  breakpoints: new Set(),
82
120
  });
121
+ const [hoveredBreakpoint, setHoveredBreakpoint] = useState<number | null>(null);
83
122
  const abortControllerRef = useRef<AbortController | null>(null);
84
123
  const resumeResolverRef = useRef<(() => void) | null>(null);
85
124
 
@@ -388,27 +427,24 @@ export function InteractionsPanel({
388
427
  // No play function available
389
428
  if (!hasPlayFunction) {
390
429
  return (
391
- <div className="h-full flex flex-col">
392
- <div className="p-4 border-b border-gray-200 dark:border-gray-700">
393
- <h3 className="font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2">
394
- <PlayIcon className="w-4 h-4" />
395
- Interactions
396
- </h3>
430
+ <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
431
+ <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
432
+ <Stack direction="row" align="center" gap="sm">
433
+ <PlayIcon style={{ width: 16, height: 16 }} />
434
+ <Text weight="medium">Interactions</Text>
435
+ </Stack>
397
436
  </div>
398
- <div className="flex-1 flex items-center justify-center p-8 text-center">
399
- <div className="max-w-md">
400
- <div className="w-12 h-12 rounded-full bg-gray-100 dark:bg-gray-800 flex items-center justify-center mx-auto mb-4">
401
- <PlayIcon className="w-6 h-6 text-gray-400" />
402
- </div>
403
- <h4 className="font-medium text-gray-700 dark:text-gray-300 mb-2">
404
- No interactions defined
405
- </h4>
406
- <p className="text-sm text-gray-500 dark:text-gray-400">
407
- This variant doesn't have a play function. Add a <code className="px-1 py-0.5 bg-gray-100 dark:bg-gray-800 rounded text-xs">play</code> function to your Storybook story to enable interaction testing.
408
- </p>
409
- <div className="mt-4 p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg text-left">
410
- <pre className="text-xs text-gray-600 dark:text-gray-400 overflow-x-auto">
411
- {`export const Default = {
437
+ <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px' }}>
438
+ <EmptyState>
439
+ <EmptyState.Icon>
440
+ <PlayIcon style={{ width: 24, height: 24 }} />
441
+ </EmptyState.Icon>
442
+ <EmptyState.Title>No interactions defined</EmptyState.Title>
443
+ <EmptyState.Description>
444
+ This variant doesn't have a play function. Add a <code style={{ padding: '2px 4px', background: 'var(--bg-secondary)', borderRadius: '4px', fontSize: '12px' }}>play</code> function to your Storybook story to enable interaction testing.
445
+ </EmptyState.Description>
446
+ <div style={{ marginTop: '16px', width: '100%' }}>
447
+ <CodeBlock language="typescript">{`export const Default = {
412
448
  play: async ({ canvasElement, step }) => {
413
449
  const canvas = within(canvasElement);
414
450
 
@@ -422,297 +458,314 @@ export function InteractionsPanel({
422
458
  canvas.getByText('Clicked!')
423
459
  ).toBeInTheDocument();
424
460
  }
425
- };`}
426
- </pre>
461
+ };`}</CodeBlock>
427
462
  </div>
428
- </div>
463
+ </EmptyState>
429
464
  </div>
430
465
  </div>
431
466
  );
432
467
  }
433
468
 
434
469
  return (
435
- <div className="h-full flex flex-col" data-interactions-panel>
470
+ <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }} data-interactions-panel>
436
471
  {/* Header */}
437
- <div className="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
438
- <h3 className="font-medium text-gray-900 dark:text-gray-100 flex items-center gap-2">
439
- <PlayIcon className="w-4 h-4" />
440
- Interactions
441
- </h3>
442
- <div className="flex items-center gap-2">
443
- {result.duration !== undefined && (
444
- <span className="text-xs text-gray-500 dark:text-gray-400">
445
- {formatDuration(result.duration)}
446
- </span>
447
- )}
448
-
449
- {/* Debug mode toggle */}
450
- <button
451
- onClick={toggleDebugMode}
452
- className={clsx(
453
- "p-1.5 rounded-md transition-colors",
454
- debugState.mode === 'debug'
455
- ? "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400"
456
- : "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800"
472
+ <div style={{ padding: '16px', borderBottom: '1px solid var(--border)' }}>
473
+ <Stack direction="row" align="center" justify="between">
474
+ <Stack direction="row" align="center" gap="sm">
475
+ <PlayIcon style={{ width: 16, height: 16 }} />
476
+ <Text weight="medium">Interactions</Text>
477
+ </Stack>
478
+ <Stack direction="row" align="center" gap="sm">
479
+ {result.duration !== undefined && (
480
+ <Text size="xs" color="tertiary">{formatDuration(result.duration)}</Text>
457
481
  )}
458
- title={debugState.mode === 'debug' ? "Exit debug mode" : "Enable debug mode (F5 to run with debugger)"}
459
- >
460
- <BugIcon className="w-4 h-4" />
461
- </button>
462
-
463
- {/* Debug controls when paused */}
464
- {debugState.isPaused && (
465
- <>
466
- <button
467
- onClick={handleResume}
468
- className="p-1.5 text-green-600 dark:text-green-400 hover:bg-green-100 dark:hover:bg-green-900/30 rounded-md transition-colors"
469
- title="Continue (F8)"
470
- >
471
- <ContinueIcon className="w-4 h-4" />
472
- </button>
473
- <button
474
- onClick={handleStepOver}
475
- className="p-1.5 text-blue-600 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/30 rounded-md transition-colors"
476
- title="Step over (F10)"
477
- >
478
- <StepOverIcon className="w-4 h-4" />
479
- </button>
480
- </>
481
- )}
482
-
483
- <button
484
- onClick={() => runInteractions(debugState.mode === 'debug')}
485
- disabled={result.status === "running"}
486
- className={clsx(
487
- "flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-colors",
488
- result.status === "running" || result.status === "paused"
489
- ? "bg-gray-100 dark:bg-gray-800 text-gray-400 cursor-not-allowed"
490
- : "bg-blue-600 hover:bg-blue-700 text-white"
491
- )}
492
- >
493
- {result.status === "running" ? (
494
- <>
495
- <LoadingIcon className="w-4 h-4 animate-spin" />
496
- Running...
497
- </>
498
- ) : result.status === "paused" ? (
499
- <>
500
- <PauseIcon className="w-4 h-4" />
501
- Paused
502
- </>
503
- ) : result.status === "idle" ? (
504
- <>
505
- <PlayIcon className="w-4 h-4" />
506
- {debugState.mode === 'debug' ? 'Debug' : 'Run'}
507
- </>
508
- ) : (
482
+
483
+ {/* Debug mode toggle */}
484
+ <Button
485
+ onClick={toggleDebugMode}
486
+ variant="ghost"
487
+ size="sm"
488
+ title={debugState.mode === 'debug' ? "Exit debug mode" : "Enable debug mode (F5 to run with debugger)"}
489
+ style={{
490
+ color: debugState.mode === 'debug' ? '#ea580c' : undefined,
491
+ background: debugState.mode === 'debug' ? 'color-mix(in srgb, #f97316 10%, transparent)' : undefined,
492
+ }}
493
+ >
494
+ <BugIcon style={{ width: 16, height: 16 }} />
495
+ </Button>
496
+
497
+ {/* Debug controls when paused */}
498
+ {debugState.isPaused && (
509
499
  <>
510
- <RefreshIcon className="w-4 h-4" />
511
- Rerun
500
+ <Button
501
+ onClick={handleResume}
502
+ variant="ghost"
503
+ size="sm"
504
+ title="Continue (F8)"
505
+ style={{ color: '#16a34a' }}
506
+ >
507
+ <ContinueIcon style={{ width: 16, height: 16 }} />
508
+ </Button>
509
+ <Button
510
+ onClick={handleStepOver}
511
+ variant="ghost"
512
+ size="sm"
513
+ title="Step over (F10)"
514
+ style={{ color: '#2563eb' }}
515
+ >
516
+ <StepOverIcon style={{ width: 16, height: 16 }} />
517
+ </Button>
512
518
  </>
513
519
  )}
514
- </button>
515
- </div>
520
+
521
+ <Button
522
+ onClick={() => runInteractions(debugState.mode === 'debug')}
523
+ disabled={result.status === "running"}
524
+ variant={result.status === "running" || result.status === "paused" ? "outline" : "solid"}
525
+ size="sm"
526
+ style={
527
+ result.status === "running" || result.status === "paused"
528
+ ? { background: 'var(--bg-secondary)', color: 'var(--text-muted)', cursor: 'not-allowed' }
529
+ : { background: 'var(--color-accent)', color: '#fff' }
530
+ }
531
+ >
532
+ {result.status === "running" ? (
533
+ <>
534
+ <LoadingIcon style={{ width: 16, height: 16, animation: 'spin 1s linear infinite' }} />
535
+ Running...
536
+ </>
537
+ ) : result.status === "paused" ? (
538
+ <>
539
+ <PauseIcon style={{ width: 16, height: 16 }} />
540
+ Paused
541
+ </>
542
+ ) : result.status === "idle" ? (
543
+ <>
544
+ <PlayIcon style={{ width: 16, height: 16 }} />
545
+ {debugState.mode === 'debug' ? 'Debug' : 'Run'}
546
+ </>
547
+ ) : (
548
+ <>
549
+ <RefreshIcon style={{ width: 16, height: 16 }} />
550
+ Rerun
551
+ </>
552
+ )}
553
+ </Button>
554
+ </Stack>
555
+ </Stack>
516
556
  </div>
517
557
 
518
558
  {/* Results */}
519
- <div className="flex-1 overflow-y-auto">
559
+ <div style={{ flex: 1, overflowY: 'auto' }}>
520
560
  {result.status === "idle" ? (
521
- <div className="p-8 text-center">
522
- <p className="text-sm text-gray-500 dark:text-gray-400">
561
+ <div style={{ padding: '32px', textAlign: 'center' }}>
562
+ <Text size="sm" color="tertiary">
523
563
  Click "Run" to execute the interaction tests
524
- </p>
564
+ </Text>
525
565
  </div>
526
566
  ) : (
527
- <div className="p-4 space-y-2">
567
+ <Stack direction="column" gap="sm" style={{ padding: '16px' }}>
528
568
  {/* Overall status */}
529
- <div
530
- className={clsx(
531
- "p-3 rounded-lg flex items-center justify-between",
532
- result.status === "running" && "bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800",
533
- result.status === "paused" && "bg-orange-50 dark:bg-orange-950/30 border border-orange-200 dark:border-orange-800",
534
- result.status === "passed" && "bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800",
535
- result.status === "failed" && "bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800"
536
- )}
537
- >
538
- <div className="flex items-center gap-2">
539
- {result.status === "running" && (
540
- <LoadingIcon className="w-5 h-5 text-blue-600 dark:text-blue-400 animate-spin" />
541
- )}
542
- {result.status === "paused" && (
543
- <PauseIcon className="w-5 h-5 text-orange-600 dark:text-orange-400" />
544
- )}
545
- {result.status === "passed" && (
546
- <CheckIcon className="w-5 h-5 text-green-600 dark:text-green-400" />
547
- )}
548
- {result.status === "failed" && (
549
- <XIcon className="w-5 h-5 text-red-600 dark:text-red-400" />
550
- )}
551
- <span
552
- className={clsx(
553
- "font-medium",
554
- result.status === "running" && "text-blue-700 dark:text-blue-300",
555
- result.status === "paused" && "text-orange-700 dark:text-orange-300",
556
- result.status === "passed" && "text-green-700 dark:text-green-300",
557
- result.status === "failed" && "text-red-700 dark:text-red-300"
558
- )}
559
- >
560
- {result.status === "running" && "Running interactions..."}
561
- {result.status === "paused" && "Paused at breakpoint"}
562
- {result.status === "passed" && "All interactions passed"}
563
- {result.status === "failed" && "Interactions failed"}
564
- </span>
565
- </div>
566
- <span className="text-xs text-gray-500 dark:text-gray-400">
567
- {result.steps.filter((s) => s.status === "passed").length}/{result.steps.length} steps
568
- </span>
569
- </div>
569
+ {(() => {
570
+ const colors = getStatusColors(result.status);
571
+ const alertVariant = result.status === 'passed' ? 'success'
572
+ : result.status === 'failed' ? 'error'
573
+ : result.status === 'paused' ? 'warning'
574
+ : 'info';
575
+ return (
576
+ <Alert variant={alertVariant}>
577
+ <Alert.Body>
578
+ <Stack direction="row" align="center" justify="between" style={{ width: '100%' }}>
579
+ <Stack direction="row" align="center" gap="sm">
580
+ {result.status === "running" && (
581
+ <LoadingIcon style={{ width: 20, height: 20, animation: 'spin 1s linear infinite', color: colors.icon }} />
582
+ )}
583
+ {result.status === "paused" && (
584
+ <PauseIcon style={{ width: 20, height: 20, color: colors.icon }} />
585
+ )}
586
+ {result.status === "passed" && (
587
+ <CheckIcon style={{ width: 20, height: 20, color: colors.icon }} />
588
+ )}
589
+ {result.status === "failed" && (
590
+ <XIcon style={{ width: 20, height: 20, color: colors.icon }} />
591
+ )}
592
+ <Text weight="medium" style={{ color: colors.text }}>
593
+ {result.status === "running" && "Running interactions..."}
594
+ {result.status === "paused" && "Paused at breakpoint"}
595
+ {result.status === "passed" && "All interactions passed"}
596
+ {result.status === "failed" && "Interactions failed"}
597
+ </Text>
598
+ </Stack>
599
+ <Text size="xs" color="tertiary">
600
+ {result.steps.filter((s) => s.status === "passed").length}/{result.steps.length} steps
601
+ </Text>
602
+ </Stack>
603
+ </Alert.Body>
604
+ </Alert>
605
+ );
606
+ })()}
570
607
 
571
608
  {/* Steps */}
572
609
  {result.steps.length > 0 && (
573
- <div className="mt-4 space-y-1">
574
- <div className="flex items-center justify-between mb-2">
575
- <h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide">
610
+ <Stack direction="column" gap="xs" style={{ marginTop: '16px' }}>
611
+ <Stack direction="row" align="center" justify="between" style={{ marginBottom: '8px' }}>
612
+ <Text size="xs" weight="medium" color="tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em' }}>
576
613
  Steps
577
- </h4>
614
+ </Text>
578
615
  {debugState.mode === 'debug' && (
579
- <span className="text-xs text-orange-600 dark:text-orange-400">
616
+ <Text size="xs" style={{ color: '#ea580c' }}>
580
617
  Debug mode - click gutter to set breakpoints
581
- </span>
618
+ </Text>
582
619
  )}
583
- </div>
584
- {result.steps.map((step, index) => (
585
- <div
586
- key={index}
587
- className={clsx(
588
- "rounded-lg border transition-colors flex",
589
- step.status === "pending" && "bg-gray-50 dark:bg-gray-800/50 border-gray-200 dark:border-gray-700",
590
- step.status === "running" && "bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-800",
591
- step.status === "paused" && "bg-orange-50 dark:bg-orange-950/30 border-orange-200 dark:border-orange-800",
592
- step.status === "passed" && "bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
593
- step.status === "failed" && "bg-red-50 dark:bg-red-950/30 border-red-200 dark:border-red-800",
594
- debugState.currentStepIndex === index && "ring-2 ring-orange-400 dark:ring-orange-500"
595
- )}
596
- >
597
- {/* Breakpoint gutter - only show in debug mode */}
598
- {debugState.mode === 'debug' && (
599
- <button
600
- onClick={(e) => {
601
- e.stopPropagation();
602
- toggleBreakpoint(index);
603
- }}
604
- className="w-6 flex-shrink-0 flex items-center justify-center border-r border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"
605
- title={debugState.breakpoints.has(index) ? "Remove breakpoint (F9)" : "Add breakpoint (F9)"}
606
- >
607
- {debugState.breakpoints.has(index) ? (
608
- <BreakpointIcon className="w-3 h-3 text-red-500" />
609
- ) : (
610
- <BreakpointEmptyIcon className="w-3 h-3 text-gray-300 dark:text-gray-600 hover:text-red-400" />
611
- )}
612
- </button>
613
- )}
614
-
615
- <div className="flex-1">
616
- <button
617
- onClick={() => step.error && toggleStep(index)}
618
- className={clsx(
619
- "w-full p-3 flex items-center gap-2 text-left",
620
- step.error && "cursor-pointer"
621
- )}
622
- disabled={!step.error}
623
- >
624
- {/* Current step indicator */}
625
- {debugState.mode === 'debug' && debugState.currentStepIndex === index && (
626
- <span className="text-orange-500 font-bold">▶</span>
627
- )}
620
+ </Stack>
621
+ {result.steps.map((step, index) => {
622
+ const stepColors = getStatusColors(step.status);
623
+ return (
624
+ <div
625
+ key={index}
626
+ style={{
627
+ borderRadius: '8px',
628
+ border: step.status === 'passed' ? '1px solid var(--border)' : stepColors.border,
629
+ transition: 'background 0.15s',
630
+ display: 'flex',
631
+ background: step.status === 'passed' ? 'var(--bg-primary)' : stepColors.bg,
632
+ outline: debugState.currentStepIndex === index ? '2px solid #f97316' : undefined,
633
+ outlineOffset: debugState.currentStepIndex === index ? '0px' : undefined,
634
+ }}
635
+ >
636
+ {/* Breakpoint gutter - only show in debug mode */}
637
+ {debugState.mode === 'debug' && (
638
+ <button
639
+ onClick={(e) => {
640
+ e.stopPropagation();
641
+ toggleBreakpoint(index);
642
+ }}
643
+ style={{
644
+ width: '24px',
645
+ flexShrink: 0,
646
+ display: 'flex',
647
+ alignItems: 'center',
648
+ justifyContent: 'center',
649
+ border: 'none',
650
+ borderRight: '1px solid var(--border)',
651
+ background: hoveredBreakpoint === index ? 'var(--bg-hover)' : 'transparent',
652
+ transition: 'background 0.15s',
653
+ cursor: 'pointer',
654
+ padding: 0,
655
+ }}
656
+ onMouseEnter={() => setHoveredBreakpoint(index)}
657
+ onMouseLeave={() => setHoveredBreakpoint(null)}
658
+ title={debugState.breakpoints.has(index) ? "Remove breakpoint (F9)" : "Add breakpoint (F9)"}
659
+ >
660
+ {debugState.breakpoints.has(index) ? (
661
+ <BreakpointIcon style={{ width: 12, height: 12, color: '#ef4444' }} />
662
+ ) : (
663
+ <BreakpointEmptyIcon style={{ width: 12, height: 12, color: hoveredBreakpoint === index ? '#f87171' : 'var(--text-muted)' }} />
664
+ )}
665
+ </button>
666
+ )}
628
667
 
629
- {/* Status icon */}
630
- {step.status === "pending" && (
631
- <div className="w-4 h-4 rounded-full border-2 border-gray-300 dark:border-gray-600" />
632
- )}
633
- {step.status === "running" && (
634
- <LoadingIcon className="w-4 h-4 text-blue-600 dark:text-blue-400 animate-spin" />
635
- )}
636
- {step.status === "paused" && (
637
- <PauseIcon className="w-4 h-4 text-orange-600 dark:text-orange-400" />
638
- )}
639
- {step.status === "passed" && (
640
- <CheckIcon className="w-4 h-4 text-green-600 dark:text-green-400" />
641
- )}
642
- {step.status === "failed" && (
643
- <XIcon className="w-4 h-4 text-red-600 dark:text-red-400" />
644
- )}
668
+ <div style={{ flex: 1 }}>
669
+ <button
670
+ onClick={() => step.error && toggleStep(index)}
671
+ style={{
672
+ width: '100%',
673
+ padding: '12px',
674
+ display: 'flex',
675
+ alignItems: 'center',
676
+ gap: '8px',
677
+ textAlign: 'left',
678
+ cursor: step.error ? 'pointer' : 'default',
679
+ border: 'none',
680
+ background: 'transparent',
681
+ color: 'inherit',
682
+ }}
683
+ disabled={!step.error}
684
+ >
685
+ {/* Current step indicator */}
686
+ {debugState.mode === 'debug' && debugState.currentStepIndex === index && (
687
+ <span style={{ color: '#f97316', fontWeight: 700 }}>&#9654;</span>
688
+ )}
645
689
 
646
- {/* Step name */}
647
- <span
648
- className={clsx(
649
- "flex-1 text-sm",
650
- step.status === "pending" && "text-gray-400 dark:text-gray-500",
651
- step.status === "running" && "text-blue-700 dark:text-blue-300",
652
- step.status === "paused" && "text-orange-700 dark:text-orange-300",
653
- step.status === "passed" && "text-gray-700 dark:text-gray-300",
654
- step.status === "failed" && "text-red-700 dark:text-red-300"
690
+ {/* Status icon */}
691
+ {step.status === "pending" && (
692
+ <div style={{ width: '16px', height: '16px', borderRadius: '9999px', border: '2px solid var(--text-muted)' }} />
693
+ )}
694
+ {step.status === "running" && (
695
+ <LoadingIcon style={{ width: 16, height: 16, animation: 'spin 1s linear infinite', color: stepColors.icon }} />
696
+ )}
697
+ {step.status === "paused" && (
698
+ <PauseIcon style={{ width: 16, height: 16, color: stepColors.icon }} />
699
+ )}
700
+ {step.status === "passed" && (
701
+ <CheckIcon style={{ width: 16, height: 16, color: stepColors.icon }} />
702
+ )}
703
+ {step.status === "failed" && (
704
+ <XIcon style={{ width: 16, height: 16, color: stepColors.icon }} />
655
705
  )}
656
- >
657
- {step.name}
658
- </span>
659
-
660
- {/* Duration */}
661
- {step.duration !== undefined && (
662
- <span className="text-xs text-gray-400 dark:text-gray-500">
663
- {formatDuration(step.duration)}
664
- </span>
665
- )}
666
706
 
667
- {/* Expand icon for errors */}
668
- {step.error && (
669
- expandedSteps.has(index) ? (
670
- <ChevronDownIcon className="w-4 h-4 text-gray-400" />
671
- ) : (
672
- <ChevronRightIcon className="w-4 h-4 text-gray-400" />
673
- )
707
+ {/* Step name */}
708
+ <Text size="sm" style={{ flex: 1, color: stepColors.text }}>
709
+ {step.name}
710
+ </Text>
711
+
712
+ {/* Duration */}
713
+ {step.duration !== undefined && (
714
+ <Text size="xs" color="tertiary">
715
+ {formatDuration(step.duration)}
716
+ </Text>
717
+ )}
718
+
719
+ {/* Expand icon for errors */}
720
+ {step.error && (
721
+ expandedSteps.has(index) ? (
722
+ <ChevronDownIcon style={{ width: 16, height: 16, color: 'var(--text-muted)' }} />
723
+ ) : (
724
+ <ChevronRightIcon style={{ width: 16, height: 16, color: 'var(--text-muted)' }} />
725
+ )
726
+ )}
727
+ </button>
728
+
729
+ {/* Error details */}
730
+ {step.error && expandedSteps.has(index) && (
731
+ <div style={{ padding: '0 12px 12px 36px' }}>
732
+ <pre style={{ fontSize: '12px', color: '#dc2626', background: 'color-mix(in srgb, #ef4444 10%, transparent)', padding: '8px', borderRadius: '4px', overflowX: 'auto', whiteSpace: 'pre-wrap' }}>
733
+ {step.error}
734
+ </pre>
735
+ </div>
674
736
  )}
675
- </button>
676
-
677
- {/* Error details */}
678
- {step.error && expandedSteps.has(index) && (
679
- <div className="px-3 pb-3 pl-9">
680
- <pre className="text-xs text-red-600 dark:text-red-400 bg-red-100 dark:bg-red-950/50 p-2 rounded overflow-x-auto whitespace-pre-wrap">
681
- {step.error}
682
- </pre>
683
- </div>
684
- )}
737
+ </div>
685
738
  </div>
686
- </div>
687
- ))}
688
- </div>
739
+ );
740
+ })}
741
+ </Stack>
689
742
  )}
690
743
 
691
744
  {/* Keyboard shortcuts help in debug mode */}
692
745
  {debugState.mode === 'debug' && (
693
- <div className="mt-4 p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg text-xs text-gray-500 dark:text-gray-400">
694
- <div className="font-medium mb-1">Keyboard shortcuts:</div>
695
- <div className="grid grid-cols-2 gap-1">
696
- <span><kbd className="px-1 bg-gray-200 dark:bg-gray-700 rounded">F5</kbd> Run with debugger</span>
697
- <span><kbd className="px-1 bg-gray-200 dark:bg-gray-700 rounded">F8</kbd> Continue</span>
698
- <span><kbd className="px-1 bg-gray-200 dark:bg-gray-700 rounded">F9</kbd> Toggle breakpoint</span>
699
- <span><kbd className="px-1 bg-gray-200 dark:bg-gray-700 rounded">F10</kbd> Step over</span>
746
+ <div style={{ marginTop: '16px', padding: '12px', background: 'var(--bg-secondary)', borderRadius: '8px' }}>
747
+ <Text size="xs" weight="medium" color="tertiary" style={{ marginBottom: '4px' }}>Keyboard shortcuts:</Text>
748
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px' }}>
749
+ <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F5</Badge> Run with debugger</Text>
750
+ <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F8</Badge> Continue</Text>
751
+ <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F9</Badge> Toggle breakpoint</Text>
752
+ <Text size="xs" color="tertiary"><Badge size="sm" variant="default">F10</Badge> Step over</Text>
700
753
  </div>
701
754
  </div>
702
755
  )}
703
756
 
704
757
  {/* Top-level error (if no steps failed) */}
705
758
  {result.error && !result.steps.some((s) => s.status === "failed") && (
706
- <div className="mt-4">
707
- <h4 className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
759
+ <div style={{ marginTop: '16px' }}>
760
+ <Text size="xs" weight="medium" color="tertiary" style={{ textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: '8px' }}>
708
761
  Error
709
- </h4>
710
- <pre className="text-xs text-red-600 dark:text-red-400 bg-red-100 dark:bg-red-950/50 p-3 rounded overflow-x-auto whitespace-pre-wrap">
762
+ </Text>
763
+ <pre style={{ fontSize: '12px', color: '#dc2626', background: 'color-mix(in srgb, #ef4444 10%, transparent)', padding: '12px', borderRadius: '4px', overflowX: 'auto', whiteSpace: 'pre-wrap' }}>
711
764
  {result.error}
712
765
  </pre>
713
766
  </div>
714
767
  )}
715
- </div>
768
+ </Stack>
716
769
  )}
717
770
  </div>
718
771
  </div>