@fragments-sdk/cli 0.7.0 → 0.7.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.
- package/dist/bin.js +245 -245
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-XHUDJNN3.js → chunk-32VIEOQY.js} +18 -18
- package/dist/chunk-32VIEOQY.js.map +1 -0
- package/dist/{chunk-CVXKXVOY.js → chunk-5ITIP3ES.js} +27 -27
- package/dist/chunk-5ITIP3ES.js.map +1 -0
- package/dist/{chunk-RVRTRESS.js → chunk-DQHWLAUV.js} +29 -29
- package/dist/chunk-DQHWLAUV.js.map +1 -0
- package/dist/{chunk-TJ34N7C7.js → chunk-GCZMFLDI.js} +30 -32
- package/dist/chunk-GCZMFLDI.js.map +1 -0
- package/dist/{chunk-6JBGU74P.js → chunk-GHYYFAQN.js} +23 -23
- package/dist/chunk-GHYYFAQN.js.map +1 -0
- package/dist/{chunk-NWQ4CJOQ.js → chunk-GKX2HPZ6.js} +40 -40
- package/dist/chunk-GKX2HPZ6.js.map +1 -0
- package/dist/{chunk-7OPWMLOE.js → chunk-U6VTHBNI.js} +110 -110
- package/dist/chunk-U6VTHBNI.js.map +1 -0
- package/dist/{core-W2HYIQW6.js → core-SFHPYR5H.js} +24 -26
- package/dist/{generate-LMTISDIJ.js → generate-54GJAWUY.js} +5 -5
- package/dist/generate-54GJAWUY.js.map +1 -0
- package/dist/index.d.ts +23 -27
- package/dist/index.js +10 -10
- package/dist/{init-7CHRKQ7P.js → init-EIM5WNMP.js} +5 -5
- package/dist/{init-7CHRKQ7P.js.map → init-EIM5WNMP.js.map} +1 -1
- package/dist/mcp-bin.js +73 -73
- package/dist/mcp-bin.js.map +1 -1
- package/dist/scan-KQBKUS64.js +12 -0
- package/dist/{service-T2L7VLTE.js → service-ED2LNCTU.js} +6 -6
- package/dist/{static-viewer-GBR7YNF3.js → static-viewer-Q4F4QP5M.js} +4 -4
- package/dist/{test-OJRXNDO2.js → test-6VN2DA3S.js} +19 -19
- package/dist/test-6VN2DA3S.js.map +1 -0
- package/dist/{tokens-3BWDESVM.js → tokens-P2B7ZAM3.js} +5 -5
- package/dist/{viewer-SUFOISZM.js → viewer-GM7IQPPB.js} +199 -199
- package/dist/viewer-GM7IQPPB.js.map +1 -0
- package/package.json +2 -2
- package/src/ai.ts +5 -5
- package/src/analyze.ts +11 -11
- package/src/bin.ts +1 -1
- package/src/build.ts +33 -33
- package/src/commands/a11y.ts +6 -6
- package/src/commands/add.ts +11 -11
- package/src/commands/audit.ts +4 -4
- package/src/commands/baseline.ts +3 -3
- package/src/commands/build.ts +8 -8
- package/src/commands/compare.ts +20 -20
- package/src/commands/context.ts +16 -16
- package/src/commands/enhance.ts +36 -36
- package/src/commands/generate.ts +1 -1
- package/src/commands/graph.ts +3 -3
- package/src/commands/init.ts +1 -1
- package/src/commands/link/figma.ts +82 -82
- package/src/commands/link/index.ts +3 -3
- package/src/commands/link/storybook.ts +9 -9
- package/src/commands/list.ts +2 -2
- package/src/commands/reset.ts +15 -15
- package/src/commands/scan.ts +27 -27
- package/src/commands/storygen.ts +24 -24
- package/src/commands/validate.ts +2 -2
- package/src/commands/verify.ts +8 -8
- package/src/core/auto-props.ts +4 -4
- package/src/core/composition.test.ts +36 -36
- package/src/core/composition.ts +19 -19
- package/src/core/config.ts +6 -6
- package/src/core/{defineSegment.ts → defineFragment.ts} +16 -22
- package/src/core/discovery.ts +6 -6
- package/src/core/figma.ts +2 -2
- package/src/core/graph-extractor.test.ts +77 -77
- package/src/core/graph-extractor.ts +32 -32
- package/src/core/importAnalyzer.ts +1 -1
- package/src/core/index.ts +22 -23
- package/src/core/loader.ts +22 -22
- package/src/core/node.ts +5 -5
- package/src/core/parser.ts +31 -31
- package/src/core/previewLoader.ts +1 -1
- package/src/core/schema.ts +16 -16
- package/src/core/storyAdapter.test.ts +87 -87
- package/src/core/storyAdapter.ts +16 -16
- package/src/core/types.ts +21 -26
- package/src/diff.ts +22 -22
- package/src/index.ts +2 -2
- package/src/mcp/server.ts +80 -80
- package/src/migrate/__tests__/utils/utils.test.ts +3 -3
- package/src/migrate/bin.ts +4 -4
- package/src/migrate/converter.ts +16 -16
- package/src/migrate/index.ts +3 -3
- package/src/migrate/migrate.ts +3 -3
- package/src/migrate/parser.ts +8 -8
- package/src/migrate/report.ts +2 -2
- package/src/migrate/types.ts +4 -4
- package/src/screenshot.ts +22 -22
- package/src/service/__tests__/props-extractor.test.ts +15 -15
- package/src/service/analytics.ts +39 -39
- package/src/service/enhance/codebase-scanner.ts +1 -1
- package/src/service/enhance/index.ts +1 -1
- package/src/service/enhance/props-extractor.ts +2 -2
- package/src/service/enhance/types.ts +2 -2
- package/src/service/index.ts +2 -2
- package/src/service/metrics-store.ts +1 -1
- package/src/service/patch-generator.ts +1 -1
- package/src/setup.ts +52 -52
- package/src/shared/dev-server-client.ts +7 -7
- package/src/shared/fragment-loader.ts +59 -0
- package/src/shared/index.ts +1 -1
- package/src/shared/types.ts +4 -4
- package/src/static-viewer.ts +35 -35
- package/src/test/discovery.ts +6 -6
- package/src/test/index.ts +5 -5
- package/src/test/reporters/console.ts +1 -1
- package/src/test/reporters/junit.ts +1 -1
- package/src/test/runner.ts +7 -7
- package/src/test/types.ts +3 -3
- package/src/test/watch.ts +9 -9
- package/src/validators.ts +26 -26
- package/src/viewer/__tests__/render-utils.test.ts +28 -28
- package/src/viewer/__tests__/viewer-integration.test.ts +4 -4
- package/src/viewer/cli/health.ts +26 -26
- package/src/viewer/components/App.tsx +79 -79
- package/src/viewer/components/BottomPanel.tsx +17 -17
- package/src/viewer/components/CodePanel.tsx +3 -3
- package/src/viewer/components/CommandPalette.tsx +11 -11
- package/src/viewer/components/ComponentGraph.tsx +28 -28
- package/src/viewer/components/ComponentHeader.tsx +2 -2
- package/src/viewer/components/ContractPanel.tsx +6 -6
- package/src/viewer/components/FigmaEmbed.tsx +9 -9
- package/src/viewer/components/HealthDashboard.tsx +17 -17
- package/src/viewer/components/InteractionsPanel.tsx +2 -2
- package/src/viewer/components/IsolatedPreviewFrame.tsx +6 -6
- package/src/viewer/components/IsolatedRender.tsx +10 -10
- package/src/viewer/components/LeftSidebar.tsx +28 -28
- package/src/viewer/components/MultiViewportPreview.tsx +14 -14
- package/src/viewer/components/PreviewArea.tsx +11 -11
- package/src/viewer/components/PreviewFrameHost.tsx +51 -51
- package/src/viewer/components/RightSidebar.tsx +9 -9
- package/src/viewer/components/Sidebar.tsx +17 -17
- package/src/viewer/components/StoryRenderer.tsx +2 -2
- package/src/viewer/components/TokenStylePanel.tsx +1 -1
- package/src/viewer/components/UsageSection.tsx +2 -2
- package/src/viewer/components/VariantMatrix.tsx +11 -11
- package/src/viewer/components/VariantRenderer.tsx +3 -3
- package/src/viewer/components/VariantTabs.tsx +2 -2
- package/src/viewer/components/_future/CreatePage.tsx +6 -6
- package/src/viewer/composition-renderer.ts +11 -11
- package/src/viewer/entry.tsx +40 -40
- package/src/viewer/hooks/useFigmaIntegration.ts +1 -1
- package/src/viewer/hooks/usePreviewBridge.ts +5 -5
- package/src/viewer/hooks/useUrlState.ts +6 -6
- package/src/viewer/index.ts +2 -2
- package/src/viewer/intelligence/healthReport.ts +17 -17
- package/src/viewer/intelligence/styleDrift.ts +1 -1
- package/src/viewer/intelligence/usageScanner.ts +1 -1
- package/src/viewer/render-template.html +1 -1
- package/src/viewer/render-utils.ts +21 -21
- package/src/viewer/server.ts +18 -18
- package/src/viewer/utils/detectRelationships.ts +22 -22
- package/src/viewer/vite-plugin.ts +213 -213
- package/dist/chunk-6JBGU74P.js.map +0 -1
- package/dist/chunk-7OPWMLOE.js.map +0 -1
- package/dist/chunk-CVXKXVOY.js.map +0 -1
- package/dist/chunk-NWQ4CJOQ.js.map +0 -1
- package/dist/chunk-RVRTRESS.js.map +0 -1
- package/dist/chunk-TJ34N7C7.js.map +0 -1
- package/dist/chunk-XHUDJNN3.js.map +0 -1
- package/dist/generate-LMTISDIJ.js.map +0 -1
- package/dist/scan-WY23TJCP.js +0 -12
- package/dist/test-OJRXNDO2.js.map +0 -1
- package/dist/viewer-SUFOISZM.js.map +0 -1
- package/src/shared/segment-loader.ts +0 -59
- /package/dist/{core-W2HYIQW6.js.map → core-SFHPYR5H.js.map} +0 -0
- /package/dist/{scan-WY23TJCP.js.map → scan-KQBKUS64.js.map} +0 -0
- /package/dist/{service-T2L7VLTE.js.map → service-ED2LNCTU.js.map} +0 -0
- /package/dist/{static-viewer-GBR7YNF3.js.map → static-viewer-Q4F4QP5M.js.map} +0 -0
- /package/dist/{tokens-3BWDESVM.js.map → tokens-P2B7ZAM3.js.map} +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { useState, useMemo, useRef, useCallback } from "react";
|
|
14
14
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
15
|
-
import type {
|
|
15
|
+
import type { FragmentVariant } from "../../core/index.js";
|
|
16
16
|
import { ErrorBoundary } from "./ErrorBoundary.js";
|
|
17
17
|
import { StoryRenderer, LoaderIndicator } from "./StoryRenderer.js";
|
|
18
18
|
import { IsolatedPreviewFrame } from "./IsolatedPreviewFrame.js";
|
|
@@ -21,11 +21,11 @@ import { getBackgroundStyle, type BackgroundOption } from "./PreviewToolbar.js";
|
|
|
21
21
|
|
|
22
22
|
interface VariantMatrixProps {
|
|
23
23
|
/** All variants to display */
|
|
24
|
-
variants:
|
|
24
|
+
variants: FragmentVariant[];
|
|
25
25
|
/** Component name for error display */
|
|
26
26
|
componentName: string;
|
|
27
|
-
/**
|
|
28
|
-
|
|
27
|
+
/** Fragment path for iframe rendering */
|
|
28
|
+
fragmentPath: string;
|
|
29
29
|
/** Current zoom level */
|
|
30
30
|
zoom: number;
|
|
31
31
|
/** Preview theme */
|
|
@@ -60,7 +60,7 @@ const VIRTUALIZATION_THRESHOLD = 12;
|
|
|
60
60
|
export function VariantMatrix({
|
|
61
61
|
variants,
|
|
62
62
|
componentName,
|
|
63
|
-
|
|
63
|
+
fragmentPath,
|
|
64
64
|
zoom,
|
|
65
65
|
previewTheme,
|
|
66
66
|
background,
|
|
@@ -204,7 +204,7 @@ export function VariantMatrix({
|
|
|
204
204
|
variant={variant}
|
|
205
205
|
index={index}
|
|
206
206
|
componentName={componentName}
|
|
207
|
-
|
|
207
|
+
fragmentPath={fragmentPath}
|
|
208
208
|
scale={effectiveScale}
|
|
209
209
|
minHeight={gridConfig.minHeight}
|
|
210
210
|
previewTheme={previewTheme}
|
|
@@ -236,7 +236,7 @@ export function VariantMatrix({
|
|
|
236
236
|
variant={variant}
|
|
237
237
|
index={index}
|
|
238
238
|
componentName={componentName}
|
|
239
|
-
|
|
239
|
+
fragmentPath={fragmentPath}
|
|
240
240
|
scale={effectiveScale}
|
|
241
241
|
minHeight={gridConfig.minHeight}
|
|
242
242
|
previewTheme={previewTheme}
|
|
@@ -256,10 +256,10 @@ export function VariantMatrix({
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
interface VariantCardProps {
|
|
259
|
-
variant:
|
|
259
|
+
variant: FragmentVariant;
|
|
260
260
|
index: number;
|
|
261
261
|
componentName: string;
|
|
262
|
-
|
|
262
|
+
fragmentPath: string;
|
|
263
263
|
scale: number;
|
|
264
264
|
minHeight: string;
|
|
265
265
|
previewTheme: "light" | "dark";
|
|
@@ -275,7 +275,7 @@ function VariantCard({
|
|
|
275
275
|
variant,
|
|
276
276
|
index,
|
|
277
277
|
componentName,
|
|
278
|
-
|
|
278
|
+
fragmentPath,
|
|
279
279
|
scale,
|
|
280
280
|
minHeight,
|
|
281
281
|
previewTheme,
|
|
@@ -378,7 +378,7 @@ function VariantCard({
|
|
|
378
378
|
>
|
|
379
379
|
{useIframeIsolation ? (
|
|
380
380
|
<IsolatedPreviewFrame
|
|
381
|
-
|
|
381
|
+
fragmentPath={fragmentPath}
|
|
382
382
|
variantName={variant.name}
|
|
383
383
|
theme={previewTheme}
|
|
384
384
|
width="100%"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import React, { type ReactNode, Suspense } from 'react';
|
|
3
|
-
import type {
|
|
3
|
+
import type { FragmentVariant } from '../../core/index.js';
|
|
4
4
|
import { ErrorBoundary } from './ErrorBoundary.js';
|
|
5
5
|
|
|
6
6
|
interface VariantRendererProps {
|
|
7
7
|
/** The variant to render */
|
|
8
|
-
variant:
|
|
8
|
+
variant: FragmentVariant;
|
|
9
9
|
|
|
10
10
|
/** Optional loading fallback for async components */
|
|
11
11
|
loadingFallback?: ReactNode;
|
|
@@ -54,7 +54,7 @@ export function VariantRenderer({
|
|
|
54
54
|
|
|
55
55
|
interface VariantGridProps {
|
|
56
56
|
/** Variants to render */
|
|
57
|
-
variants:
|
|
57
|
+
variants: FragmentVariant[];
|
|
58
58
|
|
|
59
59
|
/** Number of columns in the grid */
|
|
60
60
|
columns?: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Tabs } from '@fragments/ui';
|
|
2
|
-
import type {
|
|
2
|
+
import type { FragmentVariant } from '../../core/index.js';
|
|
3
3
|
import { PlayIcon } from './Icons.js';
|
|
4
4
|
|
|
5
5
|
interface VariantTabsProps {
|
|
6
|
-
variants:
|
|
6
|
+
variants: FragmentVariant[];
|
|
7
7
|
activeIndex: number;
|
|
8
8
|
onSelect: (index: number) => void;
|
|
9
9
|
}
|
|
@@ -158,7 +158,7 @@ export function CreatePage() {
|
|
|
158
158
|
|
|
159
159
|
const generateCssVariables = () => {
|
|
160
160
|
const palette = generateColorScheme(primaryColor, colorSchemeType);
|
|
161
|
-
let css = `/* ${formValues.projectName} - Generated by
|
|
161
|
+
let css = `/* ${formValues.projectName} - Generated by Fragments */
|
|
162
162
|
:root {
|
|
163
163
|
/* Primary Colors */
|
|
164
164
|
--color-primary: ${palette[0]};
|
|
@@ -209,7 +209,7 @@ ${currentPalette.map((c, i) => ` --palette-${i + 1}: ${c};`).join('\n')}
|
|
|
209
209
|
};
|
|
210
210
|
|
|
211
211
|
const generateTailwindConfig = () => {
|
|
212
|
-
return `// tailwind.config.js - Generated by
|
|
212
|
+
return `// tailwind.config.js - Generated by Fragments
|
|
213
213
|
/** @type {import('tailwindcss').Config} */
|
|
214
214
|
export default {
|
|
215
215
|
darkMode: 'class',
|
|
@@ -707,9 +707,9 @@ ${currentPalette.map((c, i) => ` palette${i + 1}: '${c}',`).join('\n')}
|
|
|
707
707
|
<h3 className="text-sm font-medium text-primary mb-2">Quick Start</h3>
|
|
708
708
|
<div className="flex items-center gap-2 p-3 rounded-lg bg-[--bg-tertiary] font-mono text-sm">
|
|
709
709
|
<span className="text-tertiary">$</span>
|
|
710
|
-
<code className="text-primary flex-1">npx
|
|
710
|
+
<code className="text-primary flex-1">npx fragment-ui init {formValues.projectName}</code>
|
|
711
711
|
<button
|
|
712
|
-
onClick={() => navigator.clipboard.writeText(`npx
|
|
712
|
+
onClick={() => navigator.clipboard.writeText(`npx fragment-ui init ${formValues.projectName}`)}
|
|
713
713
|
className="px-2 py-1 text-xs rounded bg-[--bg-hover] text-secondary hover:text-primary transition-colors"
|
|
714
714
|
>
|
|
715
715
|
Copy
|
|
@@ -752,7 +752,7 @@ ${currentPalette.map((c, i) => ` palette${i + 1}: '${c}',`).join('\n')}
|
|
|
752
752
|
{/* Config JSON */}
|
|
753
753
|
<div>
|
|
754
754
|
<div className="flex items-center justify-between mb-2">
|
|
755
|
-
<h3 className="text-sm font-medium text-primary">
|
|
755
|
+
<h3 className="text-sm font-medium text-primary">fragment.config.json</h3>
|
|
756
756
|
<button
|
|
757
757
|
onClick={() => navigator.clipboard.writeText(generateExportConfig())}
|
|
758
758
|
className="px-2 py-1 text-xs rounded bg-[--bg-hover] text-secondary hover:text-primary transition-colors"
|
|
@@ -779,7 +779,7 @@ ${currentPalette.map((c, i) => ` palette${i + 1}: '${c}',`).join('\n')}
|
|
|
779
779
|
const url = URL.createObjectURL(blob);
|
|
780
780
|
const a = document.createElement('a');
|
|
781
781
|
a.href = url;
|
|
782
|
-
a.download = '
|
|
782
|
+
a.download = 'fragment.config.json';
|
|
783
783
|
a.click();
|
|
784
784
|
}}
|
|
785
785
|
className="px-4 py-2 text-sm font-medium rounded-lg text-white transition-colors"
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
import type { ComponentNode } from "./jsx-parser.js";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Fragment file information for component lookup
|
|
13
13
|
*/
|
|
14
|
-
export interface
|
|
14
|
+
export interface FragmentFileInfo {
|
|
15
15
|
absolutePath: string;
|
|
16
16
|
relativePath: string;
|
|
17
17
|
componentName: string;
|
|
@@ -23,7 +23,7 @@ export interface SegmentFileInfo {
|
|
|
23
23
|
export interface CompositionRenderOptions {
|
|
24
24
|
/** The parsed component tree */
|
|
25
25
|
tree: ComponentNode | ComponentNode[];
|
|
26
|
-
/** Map of component names to their
|
|
26
|
+
/** Map of component names to their fragment file paths */
|
|
27
27
|
componentPaths: Map<string, string>;
|
|
28
28
|
/** Optional theme ('light' or 'dark') */
|
|
29
29
|
theme?: "light" | "dark";
|
|
@@ -85,9 +85,9 @@ ${imports}
|
|
|
85
85
|
for (const item of loadedComponents) {
|
|
86
86
|
if (item && item.mod) {
|
|
87
87
|
// Handle both default export and named component export
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
C[item.name] =
|
|
88
|
+
const fragment = item.mod.default;
|
|
89
|
+
if (fragment && fragment.component) {
|
|
90
|
+
C[item.name] = fragment.component;
|
|
91
91
|
} else if (typeof item.mod.default === "function") {
|
|
92
92
|
C[item.name] = item.mod.default;
|
|
93
93
|
}
|
|
@@ -312,20 +312,20 @@ function serializeValue(value: unknown): string {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
/**
|
|
315
|
-
* Build a map of component names to their
|
|
315
|
+
* Build a map of component names to their fragment file paths
|
|
316
316
|
*/
|
|
317
317
|
export function buildComponentPathMap(
|
|
318
318
|
components: string[],
|
|
319
|
-
|
|
319
|
+
fragments: Array<{ path: string; name: string; absolutePath: string }>
|
|
320
320
|
): Map<string, string> {
|
|
321
321
|
const map = new Map<string, string>();
|
|
322
322
|
|
|
323
323
|
for (const component of components) {
|
|
324
|
-
const
|
|
324
|
+
const fragment = fragments.find(
|
|
325
325
|
(s) => s.name.toLowerCase() === component.toLowerCase()
|
|
326
326
|
);
|
|
327
|
-
if (
|
|
328
|
-
map.set(component,
|
|
327
|
+
if (fragment) {
|
|
328
|
+
map.set(component, fragment.absolutePath);
|
|
329
329
|
}
|
|
330
330
|
}
|
|
331
331
|
|
package/src/viewer/entry.tsx
CHANGED
|
@@ -116,7 +116,7 @@ class AppErrorBoundary extends Component<{ children: ReactNode }, { hasError: bo
|
|
|
116
116
|
Check the browser console for more details. Common causes:
|
|
117
117
|
</p>
|
|
118
118
|
<ul style={{ color: '#6b7280', marginTop: '8px', paddingLeft: '20px', fontSize: '13px' }}>
|
|
119
|
-
<li>Syntax errors in
|
|
119
|
+
<li>Syntax errors in fragment or component files</li>
|
|
120
120
|
<li>Missing or undefined imports</li>
|
|
121
121
|
<li>Components that crash during render</li>
|
|
122
122
|
<li>Invalid props passed to components</li>
|
|
@@ -132,56 +132,56 @@ class AppErrorBoundary extends Component<{ children: ReactNode }, { hasError: bo
|
|
|
132
132
|
// Declare global types
|
|
133
133
|
declare global {
|
|
134
134
|
interface Window {
|
|
135
|
-
|
|
135
|
+
__FRAGMENTS__?: Array<{
|
|
136
136
|
path: string;
|
|
137
|
-
|
|
137
|
+
fragment: import("../core/index.js").FragmentDefinition;
|
|
138
138
|
}>;
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
__FRAGMENTS_CONFIG__?: import("../core/index.js").FragmentsConfig;
|
|
140
|
+
__FRAGMENTS_ERROR__?: string;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
type
|
|
144
|
+
type FragmentItem = {
|
|
145
145
|
path: string;
|
|
146
|
-
|
|
146
|
+
fragment: import("../core/index.js").FragmentDefinition;
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
-
// Initialize
|
|
150
|
-
let
|
|
151
|
-
let loadError: string | null = window.
|
|
149
|
+
// Initialize fragments from window or empty array
|
|
150
|
+
let fragments: FragmentItem[] = window.__FRAGMENTS__ ?? [];
|
|
151
|
+
let loadError: string | null = window.__FRAGMENTS_ERROR__ ?? null;
|
|
152
152
|
let appRoot: Root | null = null;
|
|
153
153
|
|
|
154
154
|
// Filter helper
|
|
155
|
-
function
|
|
156
|
-
return items.filter(s => s && s.
|
|
155
|
+
function filterValidFragments(items: FragmentItem[]): FragmentItem[] {
|
|
156
|
+
return items.filter(s => s && s.fragment && s.fragment.meta);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// Async loader for virtual module (runs after initial render)
|
|
160
|
-
async function
|
|
161
|
-
if (
|
|
162
|
-
// Already have
|
|
160
|
+
async function loadFragmentsFromVirtualModule(): Promise<void> {
|
|
161
|
+
if (fragments.length > 0 || loadError) {
|
|
162
|
+
// Already have fragments or error from window
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
try {
|
|
167
167
|
// @ts-expect-error Virtual module
|
|
168
168
|
const virtualModule = await import("virtual:fragments");
|
|
169
|
-
// Wait for lazy-loaded
|
|
170
|
-
if (virtualModule.
|
|
171
|
-
|
|
169
|
+
// Wait for lazy-loaded fragments
|
|
170
|
+
if (virtualModule.fragmentsPromise) {
|
|
171
|
+
fragments = await virtualModule.fragmentsPromise;
|
|
172
172
|
} else {
|
|
173
|
-
|
|
173
|
+
fragments = virtualModule.fragments;
|
|
174
174
|
}
|
|
175
|
-
|
|
175
|
+
fragments = filterValidFragments(fragments);
|
|
176
176
|
} catch (e) {
|
|
177
177
|
const error = e as Error;
|
|
178
|
-
loadError = error.message || "Failed to load
|
|
178
|
+
loadError = error.message || "Failed to load fragments";
|
|
179
179
|
console.error("[Fragments] Failed to load:", error);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
// Filter initial
|
|
184
|
-
|
|
183
|
+
// Filter initial fragments
|
|
184
|
+
fragments = filterValidFragments(fragments);
|
|
185
185
|
|
|
186
186
|
// Error display component
|
|
187
187
|
function ErrorDisplay({ error }: { error: string }) {
|
|
@@ -193,7 +193,7 @@ function ErrorDisplay({ error }: { error: string }) {
|
|
|
193
193
|
margin: '0 auto',
|
|
194
194
|
}}>
|
|
195
195
|
<h1 style={{ color: '#dc2626', marginBottom: '16px' }}>
|
|
196
|
-
Failed to Load
|
|
196
|
+
Failed to Load Fragments
|
|
197
197
|
</h1>
|
|
198
198
|
<pre style={{
|
|
199
199
|
background: '#fef2f2',
|
|
@@ -212,7 +212,7 @@ function ErrorDisplay({ error }: { error: string }) {
|
|
|
212
212
|
Check the console for more details. Common issues:
|
|
213
213
|
</p>
|
|
214
214
|
<ul style={{ color: '#6b7280', marginTop: '8px', paddingLeft: '20px' }}>
|
|
215
|
-
<li>
|
|
215
|
+
<li>Fragment files have syntax errors</li>
|
|
216
216
|
<li>Components are not exported correctly</li>
|
|
217
217
|
<li>Missing dependencies</li>
|
|
218
218
|
</ul>
|
|
@@ -226,11 +226,11 @@ if (rootElement) {
|
|
|
226
226
|
appRoot = createRoot(rootElement);
|
|
227
227
|
|
|
228
228
|
const renderApp = (showSkeleton = false) => {
|
|
229
|
-
const
|
|
230
|
-
const currentError = loadError || window.
|
|
229
|
+
const currentFragments = window.__FRAGMENTS__ ?? fragments;
|
|
230
|
+
const currentError = loadError || window.__FRAGMENTS_ERROR__;
|
|
231
231
|
|
|
232
232
|
// Show skeleton while loading
|
|
233
|
-
if (showSkeleton &&
|
|
233
|
+
if (showSkeleton && currentFragments.length === 0 && !currentError) {
|
|
234
234
|
appRoot?.render(
|
|
235
235
|
<ThemeProvider>
|
|
236
236
|
<AppSkeleton />
|
|
@@ -239,8 +239,8 @@ if (rootElement) {
|
|
|
239
239
|
return;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
// Show error if we have one and no valid
|
|
243
|
-
if (currentError &&
|
|
242
|
+
// Show error if we have one and no valid fragments
|
|
243
|
+
if (currentError && currentFragments.length === 0) {
|
|
244
244
|
appRoot?.render(<ErrorDisplay error={currentError} />);
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
@@ -249,30 +249,30 @@ if (rootElement) {
|
|
|
249
249
|
<ThemeProvider>
|
|
250
250
|
<ToastProvider position="bottom-right" duration={3000}>
|
|
251
251
|
<AppErrorBoundary>
|
|
252
|
-
<App
|
|
252
|
+
<App fragments={currentFragments} />
|
|
253
253
|
</AppErrorBoundary>
|
|
254
254
|
</ToastProvider>
|
|
255
255
|
</ThemeProvider>
|
|
256
256
|
);
|
|
257
257
|
};
|
|
258
258
|
|
|
259
|
-
// Show skeleton immediately if no
|
|
260
|
-
if (
|
|
259
|
+
// Show skeleton immediately if no fragments yet
|
|
260
|
+
if (fragments.length === 0 && !loadError) {
|
|
261
261
|
renderApp(true);
|
|
262
262
|
|
|
263
|
-
// Load
|
|
264
|
-
|
|
263
|
+
// Load fragments asynchronously and re-render
|
|
264
|
+
loadFragmentsFromVirtualModule().then(() => {
|
|
265
265
|
renderApp(false);
|
|
266
266
|
});
|
|
267
267
|
} else {
|
|
268
|
-
// We have
|
|
268
|
+
// We have fragments from window, render immediately
|
|
269
269
|
renderApp(false);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
// Listen for HMR updates from
|
|
273
|
-
window.addEventListener("
|
|
272
|
+
// Listen for HMR updates from Fragments plugin
|
|
273
|
+
window.addEventListener("fragments:update", () => {
|
|
274
274
|
console.log("[Fragments] Refreshing viewer...");
|
|
275
|
-
// Re-render with updated
|
|
275
|
+
// Re-render with updated fragments
|
|
276
276
|
renderApp(false);
|
|
277
277
|
});
|
|
278
278
|
}
|
|
@@ -285,7 +285,7 @@ if (import.meta.hot) {
|
|
|
285
285
|
|
|
286
286
|
// Subscribe to a11y invalidation events from Vite plugin
|
|
287
287
|
// @ts-expect-error Vite HMR types
|
|
288
|
-
import.meta.hot.on("
|
|
288
|
+
import.meta.hot.on("fragments:a11y-invalidate", (data: {
|
|
289
289
|
components: string[];
|
|
290
290
|
file: string;
|
|
291
291
|
timestamp: number;
|
|
@@ -30,7 +30,7 @@ export function useFigmaIntegration(options: UseFigmaIntegrationOptions = {}) {
|
|
|
30
30
|
setFigmaStyles({ status: 'loading' });
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
|
-
const response = await fetch('/
|
|
33
|
+
const response = await fetch('/fragments/figma-styles', {
|
|
34
34
|
method: 'POST',
|
|
35
35
|
headers: { 'Content-Type': 'application/json' },
|
|
36
36
|
body: JSON.stringify({ figmaUrl }),
|
|
@@ -12,7 +12,7 @@ import { useRef, useEffect, useCallback, useState, type RefObject } from 'react'
|
|
|
12
12
|
*/
|
|
13
13
|
export type ParentMessage =
|
|
14
14
|
| { type: 'init'; frameId: string }
|
|
15
|
-
| { type: 'render';
|
|
15
|
+
| { type: 'render'; fragmentPath: string; variantName: string; props?: Record<string, unknown> }
|
|
16
16
|
| { type: 'theme'; theme: 'light' | 'dark' }
|
|
17
17
|
| { type: 'resize'; width: number; height: number };
|
|
18
18
|
|
|
@@ -161,7 +161,7 @@ export function usePreviewBridge(iframeRef: RefObject<HTMLIFrameElement | null>)
|
|
|
161
161
|
* Send a render request to the iframe
|
|
162
162
|
*/
|
|
163
163
|
const render = useCallback((
|
|
164
|
-
|
|
164
|
+
fragmentPath: string,
|
|
165
165
|
variantName: string,
|
|
166
166
|
props?: Record<string, unknown>
|
|
167
167
|
) => {
|
|
@@ -180,7 +180,7 @@ export function usePreviewBridge(iframeRef: RefObject<HTMLIFrameElement | null>)
|
|
|
180
180
|
setIsRendering(true);
|
|
181
181
|
setLastError(null);
|
|
182
182
|
|
|
183
|
-
sendMessage({ type: 'render',
|
|
183
|
+
sendMessage({ type: 'render', fragmentPath, variantName, props });
|
|
184
184
|
|
|
185
185
|
// Set timeout
|
|
186
186
|
const timeoutId = setTimeout(() => {
|
|
@@ -242,7 +242,7 @@ export function usePreviewBridge(iframeRef: RefObject<HTMLIFrameElement | null>)
|
|
|
242
242
|
*/
|
|
243
243
|
export function useFrameBridge() {
|
|
244
244
|
const [renderRequest, setRenderRequest] = useState<{
|
|
245
|
-
|
|
245
|
+
fragmentPath: string;
|
|
246
246
|
variantName: string;
|
|
247
247
|
props?: Record<string, unknown>;
|
|
248
248
|
} | null>(null);
|
|
@@ -273,7 +273,7 @@ export function useFrameBridge() {
|
|
|
273
273
|
switch (message.type) {
|
|
274
274
|
case 'render':
|
|
275
275
|
setRenderRequest({
|
|
276
|
-
|
|
276
|
+
fragmentPath: message.fragmentPath,
|
|
277
277
|
variantName: message.variantName,
|
|
278
278
|
props: message.props,
|
|
279
279
|
});
|
|
@@ -290,20 +290,20 @@ export function useUrlState() {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
|
-
* Find a
|
|
294
|
-
* Generic to preserve the full type of the
|
|
293
|
+
* Find a fragment by name or path
|
|
294
|
+
* Generic to preserve the full type of the fragment
|
|
295
295
|
*/
|
|
296
|
-
export function
|
|
297
|
-
|
|
296
|
+
export function findFragmentByName<T extends { path: string; fragment: { meta: { name: string } } }>(
|
|
297
|
+
fragments: T[],
|
|
298
298
|
nameOrPath: string
|
|
299
299
|
): T | undefined {
|
|
300
300
|
// Try exact path match first
|
|
301
|
-
const byPath =
|
|
301
|
+
const byPath = fragments.find((s) => s.path === nameOrPath);
|
|
302
302
|
if (byPath) return byPath;
|
|
303
303
|
|
|
304
304
|
// Try name match (case-insensitive)
|
|
305
305
|
const nameLower = nameOrPath.toLowerCase();
|
|
306
|
-
return
|
|
306
|
+
return fragments.find((s) => s.fragment.meta.name.toLowerCase() === nameLower);
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
/**
|
package/src/viewer/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { createDevServer } from "./server.js";
|
|
|
3
3
|
export type { DevServerOptions } from "./server.js";
|
|
4
4
|
|
|
5
5
|
// Vite Plugin
|
|
6
|
-
export {
|
|
7
|
-
export type {
|
|
6
|
+
export { fragmentsPlugin } from "./vite-plugin.js";
|
|
7
|
+
export type { FragmentsPluginOptions } from "./vite-plugin.js";
|
|
8
8
|
|
|
9
9
|
// Note: Viewer components (App, Layout, etc.) are NOT exported here.
|
|
10
10
|
// They use @fragments/ui which is a Vite-only alias and can't be resolved by Node.js.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Aggregates usage, drift, and documentation data into a comprehensive health report
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { FragmentDefinition } from '../../core/index.js';
|
|
7
7
|
import type { FullScanResult, UsageScanResult } from './usageScanner.js';
|
|
8
8
|
import type { DriftReport, DriftSummary, FullDriftResult } from './styleDrift.js';
|
|
9
9
|
import { aggregateDriftReports, getWorstOffenders } from './styleDrift.js';
|
|
@@ -66,8 +66,8 @@ export interface HealthReport {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface HealthReportOptions {
|
|
69
|
-
/**
|
|
70
|
-
|
|
69
|
+
/** Fragments to analyze */
|
|
70
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>;
|
|
71
71
|
/** Usage scan results (optional) */
|
|
72
72
|
usageScan?: FullScanResult;
|
|
73
73
|
/** Drift reports (optional) */
|
|
@@ -95,10 +95,10 @@ function toGrade(score: number): 'A' | 'B' | 'C' | 'D' | 'F' {
|
|
|
95
95
|
* Calculate usage statistics from scan results
|
|
96
96
|
*/
|
|
97
97
|
function calculateUsageStats(
|
|
98
|
-
|
|
98
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>,
|
|
99
99
|
usageScan?: FullScanResult
|
|
100
100
|
): UsageStats {
|
|
101
|
-
const allComponentNames =
|
|
101
|
+
const allComponentNames = fragments.map((s) => s.fragment.meta.name);
|
|
102
102
|
|
|
103
103
|
if (!usageScan) {
|
|
104
104
|
return {
|
|
@@ -167,27 +167,27 @@ function calculateDriftStats(driftReports?: DriftReport[]): StyleDriftStats {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* Calculate documentation statistics from
|
|
170
|
+
* Calculate documentation statistics from fragments
|
|
171
171
|
*/
|
|
172
172
|
function calculateDocumentationStats(
|
|
173
|
-
|
|
173
|
+
fragments: Array<{ path: string; fragment: FragmentDefinition }>
|
|
174
174
|
): DocumentationStats {
|
|
175
175
|
const documented: string[] = [];
|
|
176
176
|
const undocumented: string[] = [];
|
|
177
177
|
const missingDescriptions: string[] = [];
|
|
178
178
|
const missingUsage: string[] = [];
|
|
179
179
|
|
|
180
|
-
for (const {
|
|
181
|
-
const name =
|
|
180
|
+
for (const { fragment } of fragments) {
|
|
181
|
+
const name = fragment.meta.name;
|
|
182
182
|
|
|
183
183
|
// Check for description
|
|
184
|
-
const hasDescription =
|
|
184
|
+
const hasDescription = fragment.meta.description && fragment.meta.description.trim().length > 10;
|
|
185
185
|
|
|
186
186
|
// Check for usage guidelines
|
|
187
|
-
const hasUsage =
|
|
187
|
+
const hasUsage = fragment.usage && (fragment.usage.when.length > 0 || fragment.usage.whenNot.length > 0);
|
|
188
188
|
|
|
189
189
|
// Check for variants
|
|
190
|
-
const hasVariants =
|
|
190
|
+
const hasVariants = fragment.variants && fragment.variants.length > 0;
|
|
191
191
|
|
|
192
192
|
if (hasDescription && hasVariants) {
|
|
193
193
|
documented.push(name);
|
|
@@ -204,8 +204,8 @@ function calculateDocumentationStats(
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
const documentationRate =
|
|
208
|
-
? Math.round((documented.length /
|
|
207
|
+
const documentationRate = fragments.length > 0
|
|
208
|
+
? Math.round((documented.length / fragments.length) * 100)
|
|
209
209
|
: 0;
|
|
210
210
|
|
|
211
211
|
return {
|
|
@@ -362,11 +362,11 @@ function calculateOverallScore(
|
|
|
362
362
|
* Generate a comprehensive health report
|
|
363
363
|
*/
|
|
364
364
|
export function generateHealthReport(options: HealthReportOptions): HealthReport {
|
|
365
|
-
const {
|
|
365
|
+
const { fragments, usageScan, driftReports, tokenData } = options;
|
|
366
366
|
|
|
367
|
-
const usage = calculateUsageStats(
|
|
367
|
+
const usage = calculateUsageStats(fragments, usageScan);
|
|
368
368
|
const styleDrift = calculateDriftStats(driftReports);
|
|
369
|
-
const documentation = calculateDocumentationStats(
|
|
369
|
+
const documentation = calculateDocumentationStats(fragments);
|
|
370
370
|
const tokens = calculateTokenStats(tokenData);
|
|
371
371
|
|
|
372
372
|
const overallScore = calculateOverallScore(usage, styleDrift, documentation, tokens);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Analyzes style drift between Figma designs and rendered components
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { FragmentDefinition, DesignToken } from '../../core/index.js';
|
|
7
7
|
|
|
8
8
|
export type DriftSeverity = 'high' | 'medium' | 'low';
|
|
9
9
|
|
|
@@ -94,7 +94,7 @@ function findFiles(
|
|
|
94
94
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
95
95
|
// Skip test and story files
|
|
96
96
|
if (entry.name.includes('.test.') || entry.name.includes('.spec.') ||
|
|
97
|
-
entry.name.includes('.stories.') || entry.name.includes('.
|
|
97
|
+
entry.name.includes('.stories.') || entry.name.includes('.fragment.')) {
|
|
98
98
|
continue;
|
|
99
99
|
}
|
|
100
100
|
files.push(fullPath);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>
|
|
6
|
+
<title>Fragments Render</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link
|