vibe-design-system 2.8.47 → 2.8.49

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-design-system",
3
- "version": "2.8.47",
3
+ "version": "2.8.49",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -74,7 +74,7 @@ const DEFAULT_SKIP_LIST = [
74
74
  "Form",
75
75
  "Toaster",
76
76
  "Toast",
77
- "Button",
77
+ // Button removed — now handled by multi-dimension detection (variant × size × disabled)
78
78
  ];
79
79
 
80
80
  // Merge default with project-specific overrides from vds.config.js
@@ -441,6 +441,305 @@ function parseUnionLiterals(type) {
441
441
  return matches.map((s) => s.replace(/"/g, ""));
442
442
  }
443
443
 
444
+ // ─── Multi-Dimension State Detection ─────────────────────────────────────────
445
+
446
+ /**
447
+ * Parse ALL CVA variant dimensions from component source (not just "variant").
448
+ * Returns: { variant: ["default", "secondary", ...], size: ["sm", "md", "lg"], ... }
449
+ */
450
+ function parseCvaAllDimensions(source) {
451
+ if (!source) return {};
452
+ // Find "variants:" or "variants :" block
453
+ const variantsIdx = source.search(/variants\s*:\s*\{/);
454
+ if (variantsIdx === -1) return {};
455
+
456
+ const braceStart = source.indexOf("{", variantsIdx + 8);
457
+ if (braceStart === -1) return {};
458
+
459
+ // Brace-count to find matching close brace
460
+ let depth = 0, braceEnd = -1;
461
+ for (let i = braceStart; i < source.length; i++) {
462
+ if (source[i] === "{") depth++;
463
+ else if (source[i] === "}") { depth--; if (depth === 0) { braceEnd = i; break; } }
464
+ }
465
+ if (braceEnd === -1) return {};
466
+
467
+ const variantsBody = source.slice(braceStart + 1, braceEnd);
468
+
469
+ // Extract each dimension key and its sub-block keys
470
+ const result = {};
471
+ const dimRe = /(\w+)\s*:\s*\{/g;
472
+ let dm;
473
+ while ((dm = dimRe.exec(variantsBody)) !== null) {
474
+ const dimName = dm[1];
475
+ const subStart = dm.index + dm[0].length - 1;
476
+ // Brace-count for this dimension's sub-block
477
+ let d = 0, subEnd = -1;
478
+ for (let i = subStart; i < variantsBody.length; i++) {
479
+ if (variantsBody[i] === "{") d++;
480
+ else if (variantsBody[i] === "}") { d--; if (d === 0) { subEnd = i; break; } }
481
+ }
482
+ if (subEnd === -1) continue;
483
+ const subBody = variantsBody.slice(subStart + 1, subEnd);
484
+ const keys = [];
485
+ const keyRe = /^\s*([A-Za-z_][\w]*)\s*:/gm;
486
+ let km;
487
+ while ((km = keyRe.exec(subBody)) !== null) {
488
+ keys.push(km[1]);
489
+ }
490
+ if (keys.length > 0) result[dimName] = keys;
491
+ }
492
+ return result;
493
+ }
494
+
495
+ /**
496
+ * Parse CVA defaultVariants block.
497
+ * Returns: { variant: "default", size: "default" }
498
+ */
499
+ function parseCvaDefaultVariants(source) {
500
+ if (!source) return {};
501
+ const idx = source.search(/defaultVariants\s*:\s*\{/);
502
+ if (idx === -1) return {};
503
+ const braceStart = source.indexOf("{", idx + 15);
504
+ if (braceStart === -1) return {};
505
+ let depth = 0, braceEnd = -1;
506
+ for (let i = braceStart; i < source.length; i++) {
507
+ if (source[i] === "{") depth++;
508
+ else if (source[i] === "}") { depth--; if (depth === 0) { braceEnd = i; break; } }
509
+ }
510
+ if (braceEnd === -1) return {};
511
+ const body = source.slice(braceStart + 1, braceEnd);
512
+ const result = {};
513
+ const re = /(\w+)\s*:\s*["']([^"']+)["']/g;
514
+ let m;
515
+ while ((m = re.exec(body)) !== null) {
516
+ result[m[1]] = m[2];
517
+ }
518
+ return result;
519
+ }
520
+
521
+ /** Known boolean prop names that represent visual states worth showing in Storybook. */
522
+ const BOOLEAN_STATE_PROP_NAMES = new Set([
523
+ "disabled", "loading", "isLoading", "active", "isActive",
524
+ "checked", "isChecked", "selected", "isSelected",
525
+ "error", "isError", "readOnly", "isReadOnly",
526
+ ]);
527
+
528
+ /**
529
+ * Detect boolean state props from effectiveProps.
530
+ * Returns: ["disabled", "loading"]
531
+ */
532
+ function detectBooleanStateProps(effectiveProps) {
533
+ if (!Array.isArray(effectiveProps)) return [];
534
+ return effectiveProps
535
+ .filter((p) => {
536
+ const type = (p.type || "").trim();
537
+ const isBool = /^boolean/.test(type) || type === "boolean | undefined" || type === "boolean | null";
538
+ return isBool && BOOLEAN_STATE_PROP_NAMES.has(p.name);
539
+ })
540
+ .map((p) => p.name);
541
+ }
542
+
543
+ /**
544
+ * Detect all string union/enum props from effectiveProps (e.g., color: "red" | "blue").
545
+ * Returns: [{ name: "color", values: ["red", "blue", "green"] }]
546
+ */
547
+ function detectUnionEnumProps(effectiveProps) {
548
+ if (!Array.isArray(effectiveProps)) return [];
549
+ const results = [];
550
+ for (const p of effectiveProps) {
551
+ const type = (p.type || "").trim();
552
+ if (/^['"][^'"]+['"](\s*\|\s*['"][^'"]+['"])+/.test(type)) {
553
+ const values = parseUnionLiterals(type);
554
+ if (values.length >= 2) {
555
+ results.push({ name: p.name, values });
556
+ }
557
+ }
558
+ }
559
+ return results;
560
+ }
561
+
562
+ /** Priority order for determining which dimension is "primary" (gets individual stories). */
563
+ const DIMENSION_PRIORITY = ["variant", "type", "status", "color", "kind", "mode", "side"];
564
+
565
+ /**
566
+ * Build a complete variant map from source + effectiveProps.
567
+ * Combines CVA dimensions, TypeScript union props, and boolean states.
568
+ */
569
+ function buildComponentVariantMap(source, effectiveProps) {
570
+ const map = { dimensions: {}, booleanStates: [], primaryDimension: null, secondaryDimensions: [] };
571
+
572
+ // 1. Parse CVA dimensions from source
573
+ const cvaDims = parseCvaAllDimensions(source);
574
+ const cvaDefaults = parseCvaDefaultVariants(source);
575
+ for (const [dimName, values] of Object.entries(cvaDims)) {
576
+ map.dimensions[dimName] = { values, default: cvaDefaults[dimName] || values[0], source: "cva" };
577
+ }
578
+
579
+ // 2. Parse union enum props from TypeScript types (only if not already from CVA)
580
+ const unionProps = detectUnionEnumProps(effectiveProps);
581
+ for (const { name, values } of unionProps) {
582
+ if (!map.dimensions[name]) {
583
+ map.dimensions[name] = { values, default: values[0], source: "typescript" };
584
+ }
585
+ }
586
+
587
+ // 3. Detect boolean state props
588
+ map.booleanStates = detectBooleanStateProps(effectiveProps);
589
+
590
+ // 4. Determine primary and secondary dimensions
591
+ const dimNames = Object.keys(map.dimensions);
592
+ for (const candidate of DIMENSION_PRIORITY) {
593
+ if (dimNames.includes(candidate)) {
594
+ map.primaryDimension = candidate;
595
+ break;
596
+ }
597
+ }
598
+ if (!map.primaryDimension && dimNames.length > 0) {
599
+ map.primaryDimension = dimNames[0];
600
+ }
601
+ map.secondaryDimensions = dimNames.filter((d) => d !== map.primaryDimension);
602
+
603
+ return map;
604
+ }
605
+
606
+ /**
607
+ * Generate multi-dimension stories: individual per variant, per size, per state, + AllVariants grid.
608
+ */
609
+ function buildMultiDimensionStories(variantMap, renderLine, childrenArgLine, defaultArgLines, componentName) {
610
+ const lines = [];
611
+ const { dimensions, booleanStates, primaryDimension, secondaryDimensions } = variantMap;
612
+
613
+ // Filter defaultArgLines to exclude dimension props (they are set explicitly per story)
614
+ const allDimNames = new Set(Object.keys(dimensions));
615
+ const filteredArgLines = defaultArgLines.filter((line) => {
616
+ const match = line.match(/^\s+(\w+)\s*:/);
617
+ return !match || !allDimNames.has(match[1]);
618
+ });
619
+
620
+ // --- A. Primary dimension stories (one per value) ---
621
+ if (primaryDimension && dimensions[primaryDimension]) {
622
+ const dim = dimensions[primaryDimension];
623
+ const defaultValue = dim.default || dim.values[0];
624
+
625
+ // Default story uses the default value
626
+ lines.push(`export const Default: Story = {`);
627
+ lines.push(renderLine);
628
+ lines.push(` args: {`);
629
+ lines.push(` ${primaryDimension}: "${defaultValue}",`);
630
+ if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
631
+ for (const line of filteredArgLines) lines.push(line);
632
+ lines.push(` },`);
633
+ lines.push(`};`);
634
+ lines.push("");
635
+
636
+ // Remaining values get their own stories
637
+ for (const val of dim.values) {
638
+ if (val === defaultValue) continue;
639
+ const storyName = capitalize(val);
640
+ lines.push(`export const ${storyName}: Story = {`);
641
+ lines.push(renderLine);
642
+ lines.push(` args: {`);
643
+ lines.push(` ${primaryDimension}: "${val}",`);
644
+ if (childrenArgLine(capitalize(val))) lines.push(childrenArgLine(capitalize(val)));
645
+ for (const line of filteredArgLines) lines.push(line);
646
+ lines.push(` },`);
647
+ lines.push(`};`);
648
+ lines.push("");
649
+ }
650
+ } else {
651
+ // No primary dimension — single Default story
652
+ lines.push(`export const Default: Story = {`);
653
+ lines.push(renderLine);
654
+ const storyArgLines = [];
655
+ if (childrenArgLine(componentName)) storyArgLines.push(childrenArgLine(componentName));
656
+ for (const line of filteredArgLines) storyArgLines.push(line);
657
+ if (storyArgLines.length > 0) {
658
+ lines.push(` args: {`);
659
+ for (const line of storyArgLines) lines.push(line);
660
+ lines.push(` },`);
661
+ }
662
+ lines.push(`};`);
663
+ lines.push("");
664
+ }
665
+
666
+ // --- B. Secondary dimension stories (one per non-default value) ---
667
+ for (const dimName of secondaryDimensions) {
668
+ const dim = dimensions[dimName];
669
+ if (!dim || dim.values.length <= 1) continue;
670
+ for (const val of dim.values) {
671
+ if (val === (dim.default || dim.values[0])) continue;
672
+ const storyName = capitalize(dimName) + capitalize(val);
673
+ lines.push(`export const ${storyName}: Story = {`);
674
+ lines.push(renderLine);
675
+ lines.push(` args: {`);
676
+ if (primaryDimension && dimensions[primaryDimension]) {
677
+ lines.push(` ${primaryDimension}: "${dimensions[primaryDimension].default || dimensions[primaryDimension].values[0]}",`);
678
+ }
679
+ lines.push(` ${dimName}: "${val}",`);
680
+ if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
681
+ for (const line of filteredArgLines) lines.push(line);
682
+ lines.push(` },`);
683
+ lines.push(`};`);
684
+ lines.push("");
685
+ }
686
+ }
687
+
688
+ // --- C. Boolean state stories ---
689
+ for (const boolProp of booleanStates) {
690
+ if (boolProp === "open" || boolProp === "isOpen") continue;
691
+ const storyName = capitalize(boolProp);
692
+ lines.push(`export const ${storyName}: Story = {`);
693
+ lines.push(renderLine);
694
+ lines.push(` args: {`);
695
+ if (primaryDimension && dimensions[primaryDimension]) {
696
+ lines.push(` ${primaryDimension}: "${dimensions[primaryDimension].default || dimensions[primaryDimension].values[0]}",`);
697
+ }
698
+ lines.push(` ${boolProp}: true,`);
699
+ if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
700
+ for (const line of filteredArgLines) lines.push(line);
701
+ lines.push(` },`);
702
+ lines.push(`};`);
703
+ lines.push("");
704
+ }
705
+
706
+ // --- D. AllVariants grid story (only if primary has 2+ values) ---
707
+ if (primaryDimension && dimensions[primaryDimension] && dimensions[primaryDimension].values.length >= 2) {
708
+ const primaryValues = dimensions[primaryDimension].values;
709
+ const secondaryDim = secondaryDimensions.find((d) => dimensions[d] && dimensions[d].values.length >= 2);
710
+
711
+ lines.push(`export const AllVariants: Story = {`);
712
+ lines.push(` render: () => (`);
713
+ lines.push(` <div style={{ display: "flex", flexDirection: "column", gap: 16, padding: 16 }}>`);
714
+
715
+ if (secondaryDim) {
716
+ const secondaryValues = dimensions[secondaryDim].values;
717
+ for (const pVal of primaryValues) {
718
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 8, alignItems: "center" }}>`);
719
+ lines.push(` <span style={{ width: 90, fontSize: 12, color: "#888", fontFamily: "monospace" }}>${pVal}</span>`);
720
+ for (const sVal of secondaryValues) {
721
+ lines.push(` <ComponentRef ${primaryDimension}="${pVal}" ${secondaryDim}="${sVal}">${pVal}</ComponentRef>`);
722
+ }
723
+ lines.push(` </div>`);
724
+ }
725
+ } else {
726
+ lines.push(` <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>`);
727
+ for (const val of primaryValues) {
728
+ lines.push(` <ComponentRef ${primaryDimension}="${val}">${val}</ComponentRef>`);
729
+ }
730
+ lines.push(` </div>`);
731
+ }
732
+
733
+ lines.push(` </div>`);
734
+ lines.push(` ),`);
735
+ lines.push(`};`);
736
+ }
737
+
738
+ return lines.join("\n");
739
+ }
740
+
741
+ // ─── End Multi-Dimension Detection ───────────────────────────────────────────
742
+
444
743
  /** Parse props with types from component source (interface/type or inline props). Returns [{ name, type, required }]. */
445
744
  function parsePropsFromSource(source) {
446
745
  if (!source || typeof source !== "string") return [];
@@ -1246,8 +1545,15 @@ function buildArgTypeEntry(prop) {
1246
1545
  * @param {Array<{name:string,type:string,required:boolean}>} effectiveProps
1247
1546
  * @returns {"SAFE"|"SECTION"|"WRAPPER"|"VARIANT"|"CONFIGURED"}
1248
1547
  */
1249
- function getStoryProfile(componentName, source, effectiveProps) {
1548
+ function getStoryProfile(componentName, source, effectiveProps, variantMap) {
1250
1549
  if (componentName && SAFE_WRAPPER_DEFAULTS[componentName]) return "SAFE";
1550
+ // Check for ANY multi-value dimensions (variant, size, color, etc.) or boolean states
1551
+ // This must run BEFORE the SECTION check because CVA components (Button, Badge) may have
1552
+ // empty effectiveProps (types come from VariantProps<typeof ...>) but still have dimensions.
1553
+ if (variantMap) {
1554
+ const hasDimensions = Object.keys(variantMap.dimensions).length > 0;
1555
+ if (hasDimensions || variantMap.booleanStates.length > 0) return "VARIANT";
1556
+ }
1251
1557
  if (effectiveProps.length === 0) return "SECTION";
1252
1558
  if (hasChildrenPropReactNode(effectiveProps)) return "WRAPPER";
1253
1559
  if (effectiveProps.some(p => p.name === "variant")) return "VARIANT";
@@ -1319,38 +1625,11 @@ function buildStoryFileContent(comp) {
1319
1625
  // Title: "Module/ComponentName" (category intentionally dropped — folder is the context)
1320
1626
  const title = `${group}/${componentName}`;
1321
1627
 
1322
- const props = Array.isArray(comp.props) ? comp.props : [];
1323
- const variantProp = props.find((p) => p.name === "variant");
1324
- let variants = parseUnionLiterals(variantProp && variantProp.type);
1325
-
1326
1628
  const srcPath = isPageFile
1327
1629
  ? path.join(PROJECT_ROOT, fileNoExt.replace(/^src\//, "src/") + ".tsx")
1328
1630
  : path.join(PROJECT_ROOT, COMPONENTS_REL_DIR, comp.file);
1329
1631
 
1330
- // Fallback: if manifest doesn't have variant metadata yet, parse cva() directly from component file.
1331
- if (!variants.length) {
1332
- try {
1333
- if (fs.existsSync(srcPath)) {
1334
- const code = fs.readFileSync(srcPath, "utf-8");
1335
- // Roughly match shadcn-style: variants: { variant: { ... }, size: { ... } }
1336
- const m = code.match(/variant\s*:\s*{([\s\S]*?)}\s*,\s*size\s*:/);
1337
- if (m) {
1338
- const body = m[1];
1339
- const names = [];
1340
- const lineRe = /^\s*([A-Za-z0-9_]+)\s*:/gm;
1341
- let lm;
1342
- while ((lm = lineRe.exec(body))) {
1343
- names.push(lm[1]);
1344
- }
1345
- if (names.length) variants = names;
1346
- }
1347
- }
1348
- } catch {
1349
- // best-effort; ignore parsing errors
1350
- }
1351
- }
1352
-
1353
- // Read component source to detect export style for import
1632
+ // Read component source once (used for export style, props, CVA parsing)
1354
1633
  let source = "";
1355
1634
  try {
1356
1635
  if (fs.existsSync(srcPath)) {
@@ -1366,9 +1645,15 @@ function buildStoryFileContent(comp) {
1366
1645
  const effectiveProps = Array.isArray(comp.props) && comp.props.length > 0 ? comp.props : parsePropsFromSource(source);
1367
1646
  const usageFromPages = findComponentUsageInPages(componentName, PROJECT_ROOT);
1368
1647
 
1648
+ // Build complete variant map: CVA dimensions + TypeScript union props + boolean states
1649
+ const variantMap = buildComponentVariantMap(source, effectiveProps);
1650
+
1651
+ // Legacy compat: extract flat variants array for buildSpecialStories
1652
+ const variants = variantMap.dimensions.variant ? variantMap.dimensions.variant.values : [];
1653
+
1369
1654
  // Profile: single source of truth — replaces omitChildren, useReactNodeChildrenRender,
1370
1655
  // isNoPropsFeature, useSafeWrapper, argsParam, propsArg flags.
1371
- const profile = getStoryProfile(componentName, source, effectiveProps);
1656
+ const profile = getStoryProfile(componentName, source, effectiveProps, variantMap);
1372
1657
  const propSummary = effectiveProps.length > 0 ? ` (${effectiveProps.length} props)` : "";
1373
1658
  console.log(`[VDS] ${componentName} → ${profile}${propSummary}`);
1374
1659
 
@@ -1439,11 +1724,15 @@ function buildStoryFileContent(comp) {
1439
1724
  lines.push(`type Story = StoryObj<typeof meta>;`);
1440
1725
  lines.push("");
1441
1726
 
1442
- // Component-specific stories (inputs, composite components, etc.)
1443
- const specialStories = buildSpecialStories(componentName, variants);
1444
- if (specialStories) {
1445
- lines.push(specialStories);
1446
- return lines.join("\n");
1727
+ // Component-specific stories for non-variant components (Input, Textarea, etc.)
1728
+ // Button and Badge are now handled generically by multi-dimension detection.
1729
+ const skipSpecialFor = new Set(["Button", "Badge"]);
1730
+ if (!skipSpecialFor.has(componentName)) {
1731
+ const specialStories = buildSpecialStories(componentName, variants);
1732
+ if (specialStories) {
1733
+ lines.push(specialStories);
1734
+ return lines.join("\n");
1735
+ }
1447
1736
  }
1448
1737
 
1449
1738
  // Profile-driven render + children — single source of truth via getStoryProfile()
@@ -1453,7 +1742,18 @@ function buildStoryFileContent(comp) {
1453
1742
  const renderLine = buildProfileRenderLine(profile, RenderTarget, argsFallback);
1454
1743
  const childrenArgLine = buildProfileChildrenArgLine(profile);
1455
1744
 
1456
- if (!variants.length) {
1745
+ // Multi-dimension story generation: variant × size × disabled × AllVariants grid
1746
+ const hasDimensions = Object.keys(variantMap.dimensions).length > 0 || variantMap.booleanStates.length > 0;
1747
+
1748
+ if (hasDimensions) {
1749
+ const multiStories = buildMultiDimensionStories(
1750
+ variantMap, renderLine, childrenArgLine, defaultArgLines, componentName
1751
+ );
1752
+ if (multiStories) {
1753
+ lines.push(multiStories);
1754
+ }
1755
+ } else {
1756
+ // No dimensions detected: single Default story
1457
1757
  lines.push(`export const Default: Story = {`);
1458
1758
  lines.push(renderLine);
1459
1759
  const storyArgLines = [];
@@ -1465,29 +1765,6 @@ function buildStoryFileContent(comp) {
1465
1765
  lines.push(` },`);
1466
1766
  }
1467
1767
  lines.push(`};`);
1468
- } else {
1469
- const defaultVariant = variants[0];
1470
- lines.push(`export const ${capitalize(defaultVariant)}: Story = {`);
1471
- lines.push(renderLine);
1472
- lines.push(` args: {`);
1473
- lines.push(` variant: "${defaultVariant}",`);
1474
- if (childrenArgLine(componentName)) lines.push(childrenArgLine(componentName));
1475
- for (const line of defaultArgLines) lines.push(line);
1476
- lines.push(` },`);
1477
- lines.push(`};`);
1478
- lines.push("");
1479
- for (const v of variants.slice(1)) {
1480
- const storyName = capitalize(v);
1481
- lines.push(`export const ${storyName}: Story = {`);
1482
- lines.push(renderLine);
1483
- lines.push(` args: {`);
1484
- lines.push(` variant: "${v}",`);
1485
- if (childrenArgLine(storyName)) lines.push(childrenArgLine(storyName));
1486
- for (const line of defaultArgLines) lines.push(line);
1487
- lines.push(` },`);
1488
- lines.push(`};`);
1489
- lines.push("");
1490
- }
1491
1768
  }
1492
1769
 
1493
1770
  return lines.join("\n");
@@ -3054,14 +3331,27 @@ function main() {
3054
3331
  // Skip unclassified and shadcn/ui primitives (UI group) — they're documented at ui.shadcn.com
3055
3332
  // Only project-specific module groups (Circles, Finance, Projects, Time, …) get stories
3056
3333
  const g = comp.group || "Components";
3334
+ // Skip shadcn/ui primitives by default — they're documented at ui.shadcn.com
3335
+ // Users can override with vds.config.js: includeGroups: ["shadcn"] or includeComponents: ["Button", "Badge"]
3336
+ const includeGroups = VDS_CONFIG.includeGroups || [];
3337
+ const includeComponents = VDS_CONFIG.includeComponents || [];
3057
3338
  if (g === "Uncategorized" || g === "UI" || g === "shadcn" || g === "ui") {
3058
- // Clean up leftover story files for components that are now in the skip group
3059
- const oldStory = path.join(STORIES_DIR, `${componentName}.stories.tsx`);
3060
- if (fs.existsSync(oldStory)) {
3061
- fs.unlinkSync(oldStory);
3062
- console.log(`[VDS] Removed shadcn/ui story: ${componentName}.stories.tsx`);
3339
+ const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === g.toLowerCase());
3340
+ const compIncluded = includeComponents.includes(componentName);
3341
+ if (!groupIncluded && !compIncluded) {
3342
+ // Clean up leftover story files for components that are now in the skip group
3343
+ const oldStory = path.join(STORIES_DIR, `${componentName}.stories.tsx`);
3344
+ if (fs.existsSync(oldStory)) {
3345
+ try {
3346
+ const firstLine = fs.readFileSync(oldStory, "utf-8").split("\n")[0] || "";
3347
+ if (firstLine.includes("@vds-regenerate")) {
3348
+ fs.unlinkSync(oldStory);
3349
+ console.log(`[VDS] Removed shadcn/ui story: ${componentName}.stories.tsx`);
3350
+ }
3351
+ } catch { /* ignore */ }
3352
+ }
3353
+ continue;
3063
3354
  }
3064
- continue;
3065
3355
  }
3066
3356
  if (onlyName && componentName !== onlyName) continue;
3067
3357