@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
@@ -1,7 +1,14 @@
1
- import { useState, useEffect } from 'react';
2
- import clsx from 'clsx';
1
+ import { Button, Menu, Stack, Input, Text, Box } from '@fragments/ui';
3
2
  import { VIEWPORT_PRESETS, type ViewportPreset } from '../constants/ui.js';
4
- import { ViewportIcon, ChevronDownIcon } from './Icons.js';
3
+ import {
4
+ ViewportIcon,
5
+ ChevronDownIcon,
6
+ ResponsiveIcon,
7
+ DesktopIcon,
8
+ TabletIcon,
9
+ MobileIcon,
10
+ SettingsIcon,
11
+ } from './Icons.js';
5
12
 
6
13
  // Re-export for consumers
7
14
  export type { ViewportPreset };
@@ -16,9 +23,24 @@ const VIEWPORT_PRESETS_UI = Object.entries(VIEWPORT_PRESETS).map(([value, config
16
23
  value: value as ViewportPreset,
17
24
  label: config.label,
18
25
  width: config.width,
19
- icon: config.icon,
20
26
  }));
21
27
 
28
+ function PresetIcon({ preset }: { preset: ViewportPreset }) {
29
+ const commonStyle = { width: '16px', height: '16px' };
30
+
31
+ switch (preset) {
32
+ case 'desktop':
33
+ return <DesktopIcon style={commonStyle} />;
34
+ case 'tablet':
35
+ return <TabletIcon style={commonStyle} />;
36
+ case 'mobile':
37
+ return <MobileIcon style={commonStyle} />;
38
+ case 'responsive':
39
+ default:
40
+ return <ResponsiveIcon style={commonStyle} />;
41
+ }
42
+ }
43
+
22
44
  interface ViewportSelectorProps {
23
45
  viewport: ViewportPreset;
24
46
  customSize: ViewportSize;
@@ -32,128 +54,113 @@ export function ViewportSelector({
32
54
  onViewportChange,
33
55
  onCustomSizeChange,
34
56
  }: ViewportSelectorProps) {
35
- const [isOpen, setIsOpen] = useState(false);
36
- const [showCustom, setShowCustom] = useState(false);
37
-
38
- // Close dropdown when clicking outside
39
- useEffect(() => {
40
- const handleClick = () => {
41
- setIsOpen(false);
42
- };
43
- if (isOpen) {
44
- document.addEventListener('click', handleClick);
45
- return () => document.removeEventListener('click', handleClick);
46
- }
47
- }, [isOpen]);
48
-
49
57
  const currentPreset = VIEWPORT_PRESETS_UI.find(p => p.value === viewport);
58
+ const isCustomViewport = viewport === 'custom';
50
59
  const displayLabel = viewport === 'custom'
51
- ? `${customSize.width || '?'}×${customSize.height || 'auto'}`
60
+ ? `${customSize.width || '?'}\u00D7${customSize.height || 'auto'}`
52
61
  : currentPreset?.label || 'Responsive';
53
62
 
54
63
  return (
55
- <div className="relative">
56
- <button
57
- onClick={(e) => {
58
- e.stopPropagation();
59
- setIsOpen(!isOpen);
60
- }}
61
- className={clsx(
62
- 'flex items-center gap-1.5 px-2 py-1 text-xs font-medium rounded',
63
- 'text-secondary hover:text-primary',
64
- 'hover:bg-[--bg-hover] transition-colors',
65
- 'focus:outline-none focus-visible:ring-2 focus-visible:ring-[--color-accent]'
66
- )}
67
- title="Viewport size"
68
- >
69
- <ViewportIcon className="w-3.5 h-3.5" />
70
- <span>{displayLabel}</span>
71
- <ChevronDownIcon className="w-3 h-3" />
72
- </button>
73
-
74
- {isOpen && (
75
- <div
76
- className="absolute top-full right-0 mt-1 py-1 min-w-[160px] bg-[--bg-elevated] border border-[--border] rounded-lg shadow-lg z-50"
77
- onClick={(e) => e.stopPropagation()}
64
+ <Menu>
65
+ <Menu.Trigger asChild>
66
+ <Button variant="ghost" size="sm" title="Viewport size">
67
+ <Stack direction="row" gap="xs" align="center">
68
+ <span style={{ display: 'inline-flex', width: '14px', height: '14px' }}>
69
+ <ViewportIcon />
70
+ </span>
71
+ <span>{displayLabel}</span>
72
+ <span style={{ display: 'inline-flex', width: '12px', height: '12px' }}>
73
+ <ChevronDownIcon />
74
+ </span>
75
+ </Stack>
76
+ </Button>
77
+ </Menu.Trigger>
78
+ <Menu.Content side="bottom" align="end">
79
+ <Menu.RadioGroup
80
+ value={isCustomViewport ? 'custom' : viewport}
81
+ onValueChange={(value: string) => {
82
+ if (value === 'custom') {
83
+ onViewportChange('custom');
84
+ return;
85
+ }
86
+ onViewportChange(value as ViewportPreset);
87
+ }}
78
88
  >
79
89
  {VIEWPORT_PRESETS_UI.map((preset) => (
80
- <button
81
- key={preset.value}
82
- onClick={() => {
83
- onViewportChange(preset.value);
84
- setShowCustom(false);
85
- setIsOpen(false);
86
- }}
87
- className={clsx(
88
- 'w-full px-3 py-1.5 text-xs text-left flex items-center justify-between',
89
- 'hover:bg-[--bg-hover] transition-colors',
90
- preset.value === viewport ? 'text-[--color-accent] font-medium' : 'text-secondary'
91
- )}
92
- >
93
- <span className="flex items-center gap-2">
94
- <span>{preset.icon}</span>
95
- {preset.label}
96
- </span>
97
- {preset.width && (
98
- <span className="text-[10px] text-tertiary">{preset.width}px</span>
99
- )}
100
- </button>
90
+ <Menu.RadioItem key={preset.value} value={preset.value}>
91
+ <Stack direction="row" gap="sm" align="center" justify="between" style={{ width: '100%' }}>
92
+ <Stack direction="row" gap="sm" align="center">
93
+ <PresetIcon preset={preset.value} />
94
+ <span>{preset.label}</span>
95
+ </Stack>
96
+ {preset.width && (
97
+ <Text size="2xs" color="tertiary">
98
+ {preset.width}px
99
+ </Text>
100
+ )}
101
+ </Stack>
102
+ </Menu.RadioItem>
101
103
  ))}
104
+ </Menu.RadioGroup>
102
105
 
103
- <div className="border-t border-[--border-subtle] my-1" />
106
+ <Menu.Separator />
104
107
 
105
- <button
106
- onClick={() => {
107
- setShowCustom(!showCustom);
108
- }}
109
- className={clsx(
110
- 'w-full px-3 py-1.5 text-xs text-left flex items-center gap-2',
111
- 'hover:bg-[--bg-hover] transition-colors',
112
- viewport === 'custom' ? 'text-[--color-accent] font-medium' : 'text-secondary'
113
- )}
114
- >
115
- <span>⚙</span>
108
+ <Menu.Item onSelect={() => onViewportChange('custom')}>
109
+ <Stack direction="row" gap="sm" align="center" style={{
110
+ width: '100%',
111
+ fontWeight: isCustomViewport ? 600 : 400,
112
+ color: isCustomViewport ? 'var(--color-accent)' : undefined,
113
+ }}>
114
+ <SettingsIcon style={{ width: '16px', height: '16px' }} />
116
115
  Custom size
117
- <ChevronDownIcon className={clsx('w-3 h-3 ml-auto transition-transform', showCustom && 'rotate-180')} />
118
- </button>
116
+ </Stack>
117
+ </Menu.Item>
119
118
 
120
- {showCustom && (
121
- <div className="px-3 py-2 space-y-2">
122
- <div className="flex items-center gap-2">
123
- <label className="text-[10px] text-tertiary w-8">W:</label>
124
- <input
125
- type="number"
126
- value={customSize.width || ''}
127
- onChange={(e) => {
128
- const width = e.target.value ? parseInt(e.target.value, 10) : null;
129
- onCustomSizeChange({ ...customSize, width });
130
- onViewportChange('custom');
131
- }}
132
- placeholder="auto"
133
- className="w-16 px-2 py-1 text-[11px] font-mono bg-[--bg-primary] border border-[--border] rounded text-primary"
134
- />
135
- <span className="text-[10px] text-tertiary">px</span>
136
- </div>
137
- <div className="flex items-center gap-2">
138
- <label className="text-[10px] text-tertiary w-8">H:</label>
139
- <input
140
- type="number"
141
- value={customSize.height || ''}
142
- onChange={(e) => {
143
- const height = e.target.value ? parseInt(e.target.value, 10) : null;
144
- onCustomSizeChange({ ...customSize, height });
145
- onViewportChange('custom');
146
- }}
147
- placeholder="auto"
148
- className="w-16 px-2 py-1 text-[11px] font-mono bg-[--bg-primary] border border-[--border] rounded text-primary"
149
- />
150
- <span className="text-[10px] text-tertiary">px</span>
151
- </div>
152
- </div>
153
- )}
154
- </div>
155
- )}
156
- </div>
119
+ {isCustomViewport && (
120
+ <div
121
+ onClick={(e) => e.stopPropagation()}
122
+ onKeyDown={(e) => e.stopPropagation()}
123
+ >
124
+ <Box padding="sm">
125
+ <Stack gap="sm">
126
+ <Stack direction="row" gap="sm" align="center">
127
+ <Text size="2xs" color="tertiary" style={{ width: '20px' }}>W:</Text>
128
+ <Input
129
+ type="number"
130
+ size="sm"
131
+ value={customSize.width != null ? String(customSize.width) : ''}
132
+ onChange={(value) => {
133
+ const width = value ? parseInt(value, 10) : null;
134
+ onCustomSizeChange({ ...customSize, width });
135
+ onViewportChange('custom');
136
+ }}
137
+ placeholder="auto"
138
+ style={{ width: '64px' }}
139
+ />
140
+ <Text size="2xs" color="tertiary">px</Text>
141
+ </Stack>
142
+ <Stack direction="row" gap="sm" align="center">
143
+ <Text size="2xs" color="tertiary" style={{ width: '20px' }}>H:</Text>
144
+ <Input
145
+ type="number"
146
+ size="sm"
147
+ value={customSize.height != null ? String(customSize.height) : ''}
148
+ onChange={(value) => {
149
+ const height = value ? parseInt(value, 10) : null;
150
+ onCustomSizeChange({ ...customSize, height });
151
+ onViewportChange('custom');
152
+ }}
153
+ placeholder="auto"
154
+ style={{ width: '64px' }}
155
+ />
156
+ <Text size="2xs" color="tertiary">px</Text>
157
+ </Stack>
158
+ </Stack>
159
+ </Box>
160
+ </div>
161
+ )}
162
+ </Menu.Content>
163
+ </Menu>
157
164
  );
158
165
  }
159
166
 
@@ -58,37 +58,38 @@ export function getStatusConfig(status: string | undefined) {
58
58
 
59
59
  /**
60
60
  * Relationship type styling for related components.
61
+ * Uses inline-style-compatible color values (not Tailwind classes).
61
62
  */
62
63
  export const RELATIONSHIP_CONFIG = {
63
64
  alternative: {
64
65
  label: 'Alternative',
65
- bg: 'bg-blue-500/10',
66
- text: 'text-blue-500',
66
+ bgColor: 'rgba(59, 130, 246, 0.1)',
67
+ textColor: '#3b82f6',
67
68
  },
68
69
  parent: {
69
70
  label: 'Parent',
70
- bg: 'bg-purple-500/10',
71
- text: 'text-purple-500',
71
+ bgColor: 'rgba(147, 51, 234, 0.1)',
72
+ textColor: '#a855f7',
72
73
  },
73
74
  child: {
74
75
  label: 'Child',
75
- bg: 'bg-emerald-500/10',
76
- text: 'text-emerald-500',
76
+ bgColor: 'rgba(16, 185, 129, 0.1)',
77
+ textColor: '#10b981',
77
78
  },
78
79
  sibling: {
79
80
  label: 'Sibling',
80
- bg: 'bg-amber-500/10',
81
- text: 'text-amber-500',
81
+ bgColor: 'rgba(245, 158, 11, 0.1)',
82
+ textColor: '#f59e0b',
82
83
  },
83
84
  related: {
84
85
  label: 'Related',
85
- bg: 'bg-[--bg-tertiary]',
86
- text: 'text-secondary',
86
+ bgColor: 'var(--bg-tertiary)',
87
+ textColor: 'var(--text-secondary)',
87
88
  },
88
89
  composition: {
89
90
  label: 'Uses',
90
- bg: 'bg-cyan-500/10',
91
- text: 'text-cyan-500',
91
+ bgColor: 'rgba(6, 182, 212, 0.1)',
92
+ textColor: '#06b6d4',
92
93
  },
93
94
  } as const;
94
95
 
@@ -117,10 +118,10 @@ export type ZoomLevel = (typeof ZOOM_LEVELS)[number];
117
118
  * Viewport presets.
118
119
  */
119
120
  export const VIEWPORT_PRESETS = {
120
- responsive: { label: 'Responsive', width: null, icon: '' },
121
- desktop: { label: 'Desktop', width: 1280, icon: '🖥' },
122
- tablet: { label: 'Tablet', width: 768, icon: '📱' },
123
- mobile: { label: 'Mobile', width: 375, icon: '📱' },
121
+ responsive: { label: 'Responsive', width: null, icon: 'responsive' },
122
+ desktop: { label: 'Desktop', width: 1280, icon: 'desktop' },
123
+ tablet: { label: 'Tablet', width: 768, icon: 'tablet' },
124
+ mobile: { label: 'Mobile', width: 375, icon: 'mobile' },
124
125
  } as const;
125
126
 
126
127
  export type ViewportPreset = keyof typeof VIEWPORT_PRESETS | 'custom';
@@ -167,18 +168,18 @@ export const STORAGE_KEYS = {
167
168
  export const HMR_STATUS = {
168
169
  connected: {
169
170
  label: 'Connected',
170
- color: 'text-emerald-500',
171
- bg: 'bg-emerald-500',
171
+ color: '#10b981',
172
+ bg: '#10b981',
172
173
  },
173
174
  reconnecting: {
174
175
  label: 'Reconnecting...',
175
- color: 'text-amber-500',
176
- bg: 'bg-amber-500',
176
+ color: '#f59e0b',
177
+ bg: '#f59e0b',
177
178
  },
178
179
  disconnected: {
179
180
  label: 'Disconnected',
180
- color: 'text-red-500',
181
- bg: 'bg-red-500',
181
+ color: '#ef4444',
182
+ bg: '#ef4444',
182
183
  },
183
184
  } as const;
184
185
 
@@ -2,10 +2,13 @@ import { createRoot, type Root } from "react-dom/client";
2
2
  import { Component, type ReactNode, type ErrorInfo } from "react";
3
3
  import { App } from "./components/App.js";
4
4
  import { ThemeProvider } from "./components/ThemeProvider.js";
5
+ import { ToastProvider } from "./components/Toast.js";
5
6
  import { AppSkeleton } from "./components/SkeletonLoader.js";
6
7
  // Viewer shell styles - independent from UI library
7
8
  // UI library styles are only loaded in the isolated preview area
8
9
  import "./styles/globals.css";
10
+ // Fragments UI globals - adds --fui-* CSS variables for dogfooding UI components
11
+ import "@fragments/ui";
9
12
 
10
13
  // App-level error boundary that catches any unhandled errors
11
14
  class AppErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean; error: Error | null; errorInfo: ErrorInfo | null }> {
@@ -244,9 +247,11 @@ if (rootElement) {
244
247
 
245
248
  appRoot?.render(
246
249
  <ThemeProvider>
247
- <AppErrorBoundary>
248
- <App segments={currentSegments} />
249
- </AppErrorBoundary>
250
+ <ToastProvider position="bottom-right" duration={3000}>
251
+ <AppErrorBoundary>
252
+ <App segments={currentSegments} />
253
+ </AppErrorBoundary>
254
+ </ToastProvider>
250
255
  </ThemeProvider>
251
256
  );
252
257
  };
@@ -6,9 +6,6 @@ export type { DevServerOptions } from "./server.js";
6
6
  export { segmentsPlugin } from "./vite-plugin.js";
7
7
  export type { SegmentsPluginOptions } from "./vite-plugin.js";
8
8
 
9
- // Components
10
- export { App } from "./components/App.js";
11
- export { Layout } from "./components/Layout.js";
12
- export { LeftSidebar } from "./components/LeftSidebar.js";
13
- export { RightSidebar } from "./components/RightSidebar.js";
14
- export { ThemeProvider, useTheme } from "./components/ThemeProvider.js";
9
+ // Note: Viewer components (App, Layout, etc.) are NOT exported here.
10
+ // They use @fragments/ui which is a Vite-only alias and can't be resolved by Node.js.
11
+ // Vite serves these components directly from source during development.
@@ -4,12 +4,6 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Fragment Preview</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
11
- rel="stylesheet"
12
- />
13
7
  <style>
14
8
  /* Reset and base styles for isolated preview */
15
9
  *, *::before, *::after {
@@ -19,27 +13,42 @@
19
13
  html, body {
20
14
  margin: 0;
21
15
  padding: 0;
22
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ font-family: var(--fui-font-sans, 'Geist Sans', Inter, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
23
17
  -webkit-font-smoothing: antialiased;
24
18
  -moz-osx-font-smoothing: grayscale;
25
19
  width: 100%;
26
20
  height: 100%;
27
- overflow: auto;
21
+ overflow: hidden;
28
22
  }
29
23
 
30
24
  body {
31
- background: transparent;
25
+ background: var(--fui-bg-primary, #ffffff);
32
26
  min-height: 100%;
33
27
  }
34
28
 
35
29
  #preview-root {
36
30
  width: 100%;
37
- min-height: 100%;
31
+ min-height: 100vh;
38
32
  display: flex;
39
- align-items: center;
40
- justify-content: center;
33
+ align-items: flex-start;
34
+ justify-content: flex-start;
41
35
  padding: 24px;
42
36
  box-sizing: border-box;
37
+ overflow: auto;
38
+ }
39
+
40
+ body[data-preview-mode='centered'] #preview-root {
41
+ align-items: center;
42
+ justify-content: center;
43
+ }
44
+
45
+ body[data-preview-mode='full-bleed'] #preview-root {
46
+ padding: 0;
47
+ min-height: 100vh;
48
+ }
49
+
50
+ body[data-preview-mode='full-bleed'] #preview-root > * {
51
+ width: 100%;
43
52
  }
44
53
 
45
54
  /* Hide scrollbars but allow scrolling */
@@ -73,6 +82,19 @@
73
82
  font-family: 'JetBrains Mono', ui-monospace, monospace;
74
83
  }
75
84
 
85
+ /* Visually hidden but accessible to screen readers */
86
+ .sr-only {
87
+ position: absolute;
88
+ width: 1px;
89
+ height: 1px;
90
+ padding: 0;
91
+ margin: -1px;
92
+ overflow: hidden;
93
+ clip: rect(0, 0, 0, 0);
94
+ white-space: nowrap;
95
+ border-width: 0;
96
+ }
97
+
76
98
  /* Loading indicator */
77
99
  .preview-loading {
78
100
  display: flex;
@@ -97,13 +119,16 @@
97
119
  }
98
120
  </style>
99
121
  </head>
100
- <body>
101
- <div id="preview-root">
102
- <div class="preview-loading">
103
- <div class="spinner"></div>
104
- <span>Loading preview...</span>
122
+ <body data-preview-mode="centered">
123
+ <main>
124
+ <h1 class="sr-only">Component Preview</h1>
125
+ <div id="preview-root">
126
+ <div class="preview-loading">
127
+ <div class="spinner"></div>
128
+ <span>Loading preview...</span>
129
+ </div>
105
130
  </div>
106
- </div>
131
+ </main>
107
132
  <script type="module" src="/src/preview-frame-entry.tsx"></script>
108
133
  </body>
109
134
  </html>
@@ -18,8 +18,6 @@ import {
18
18
  type InlineConfig,
19
19
  } from "vite";
20
20
  import react from "@vitejs/plugin-react";
21
- import tailwindcss from "tailwindcss";
22
- import autoprefixer from "autoprefixer";
23
21
  import { resolve, dirname, join } from "node:path";
24
22
  import { existsSync, realpathSync } from "node:fs";
25
23
  import { fileURLToPath } from "node:url";
@@ -30,8 +28,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
30
28
  // At runtime, __dirname is dist/. Viewer assets live in src/viewer/.
31
29
  const cliPackageRoot = resolve(__dirname, "..");
32
30
  const viewerRoot = resolve(cliPackageRoot, "src/viewer");
33
- const viewerTailwindConfig = resolve(viewerRoot, "tailwind.config.js");
34
31
  const packagesRoot = resolve(cliPackageRoot, "..");
32
+ const uiLibRoot = resolve(packagesRoot, "../libs/ui/src");
35
33
 
36
34
  export interface DevServerOptions {
37
35
  /** Port to run the server on */
@@ -124,8 +122,8 @@ export async function createDevServer(
124
122
  port,
125
123
  open: open ? "/fragments/" : false,
126
124
  fs: {
127
- // Allow serving files from viewer package, project, and node_modules root
128
- allow: [viewerRoot, projectRoot, configDir, dirname(nodeModulesPath), ...installedPkgRoots],
125
+ // Allow serving files from viewer package, project, UI library, and node_modules root
126
+ allow: [viewerRoot, uiLibRoot, projectRoot, configDir, dirname(nodeModulesPath), ...installedPkgRoots],
129
127
  },
130
128
  },
131
129
 
@@ -141,17 +139,8 @@ export async function createDevServer(
141
139
  }),
142
140
  ],
143
141
 
144
- // CSS configuration for viewer's Tailwind
145
- css: {
146
- postcss: {
147
- plugins: [
148
- tailwindcss({
149
- config: viewerTailwindConfig,
150
- }),
151
- autoprefixer(),
152
- ],
153
- },
154
- },
142
+ // CSS configuration
143
+ css: {},
155
144
 
156
145
  optimizeDeps: {
157
146
  // Include common dependencies for faster startup
@@ -165,6 +154,8 @@ export async function createDevServer(
165
154
  alias: {
166
155
  // Allow importing from viewer package
167
156
  "@fragments/viewer": viewerRoot,
157
+ // Resolve @fragments/ui to the UI library source for dogfooding
158
+ "@fragments/ui": resolve(uiLibRoot, "index.ts"),
168
159
  // Resolve @fragments/core to the consolidated core source
169
160
  "@fragments/core": resolve(cliPackageRoot, "src/core/index.ts"),
170
161
  // Ensure ALL react imports resolve to project's node_modules