vibe-design-system 2.8.65 → 2.8.67

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.65",
3
+ "version": "2.8.67",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -2505,6 +2505,7 @@ function writeFoundationsStories(foundations) {
2505
2505
  .slice(0, 60); // cap to avoid extremely long stories
2506
2506
 
2507
2507
  const colorsContent = [
2508
+ "import React from \"react\";",
2508
2509
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2509
2510
  "",
2510
2511
  "const meta = { title: \"Foundations/Colors\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -2630,6 +2631,7 @@ function writeFoundationsStories(foundations) {
2630
2631
  const sansFamily = typo.fontSans || typo.tailwindSans || typo.body || "system-ui, sans-serif";
2631
2632
 
2632
2633
  const typoContent = [
2634
+ "import React from \"react\";",
2633
2635
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2634
2636
  "",
2635
2637
  "const meta = { title: \"Foundations/Typography\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -2790,6 +2792,7 @@ function writeFoundationsStories(foundations) {
2790
2792
 
2791
2793
  const brandContent =
2792
2794
  [
2795
+ "import React from \"react\";",
2793
2796
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2794
2797
  "",
2795
2798
  "const meta = { title: \"Foundations/Brand\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -2823,6 +2826,7 @@ function writeFoundationsStories(foundations) {
2823
2826
 
2824
2827
  const iconsContent = [
2825
2828
  "import { useState } from \"react\";",
2829
+ "import React from \"react\";",
2826
2830
  "import type { Meta, StoryObj } from \"@storybook/react\";",
2827
2831
  "import * as Lucide from \"lucide-react\";",
2828
2832
  "",
@@ -2997,6 +3001,7 @@ function writeFoundationsStories(foundations) {
2997
3001
  const containerCount = gridSystem.containerCount || 0;
2998
3002
 
2999
3003
  const gridContent = [
3004
+ "import React from \"react\";",
3000
3005
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3001
3006
  "",
3002
3007
  "const meta = { title: \"Foundations/Grid\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -3186,6 +3191,7 @@ function writeFoundationsStories(foundations) {
3186
3191
  }));
3187
3192
  const buttonsContent =
3188
3193
  [
3194
+ "import React from \"react\";",
3189
3195
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3190
3196
  "",
3191
3197
  "const meta = { title: \"Foundations/Button Usage\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -3261,6 +3267,7 @@ function writeFoundationsStories(foundations) {
3261
3267
 
3262
3268
  const usedSpacing = (foundations?.tokenUsage?.spacing || []).slice(0, 16);
3263
3269
  const spacingContent = [
3270
+ "import React from \"react\";",
3264
3271
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3265
3272
  "",
3266
3273
  "const meta = { title: \"Foundations/Spacing & Layout\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -3368,6 +3375,7 @@ function writeFoundationsStories(foundations) {
3368
3375
  const zIndexSemantics = { "z-0": "Base", "z-10": "Low / hover", "z-20": "Dropdown", "z-30": "Sticky", "z-40": "Fixed", "z-50": "Modal / overlay" };
3369
3376
 
3370
3377
  const elevationContent = [
3378
+ "import React from \"react\";",
3371
3379
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3372
3380
  "",
3373
3381
  "const meta = { title: \"Foundations/Elevation & Shadows\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -3490,6 +3498,7 @@ function writeFoundationsStories(foundations) {
3490
3498
  const usedRadius = (foundations?.tokenUsage?.radius || []).slice(0, 12);
3491
3499
 
3492
3500
  const borderContent = [
3501
+ "import React from \"react\";",
3493
3502
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3494
3503
  "",
3495
3504
  "const meta = { title: \"Foundations/Border & Radius\", parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -3593,6 +3602,7 @@ function writeFoundationsStories(foundations) {
3593
3602
  const motionChips = (foundations?.tokenUsage?.animations || []).slice(0, 24);
3594
3603
 
3595
3604
  const motionContent = [
3605
+ "import React from \"react\";",
3596
3606
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3597
3607
  "import { useState, useEffect } from \"react\";",
3598
3608
  "",
@@ -3743,6 +3753,7 @@ function writeComponentSuggestionsStory(componentSuggestions) {
3743
3753
  }));
3744
3754
  const content =
3745
3755
  [
3756
+ "import React from \"react\";",
3746
3757
  "import type { Meta, StoryObj } from \"@storybook/react\";",
3747
3758
  "import { useState } from \"react\";",
3748
3759
  "",
@@ -3950,6 +3961,58 @@ function buildInventoryPreviewHtml(compName, category, colorSwatches, tokens) {
3950
3961
  * Shows all project components classified by Atlassian-style functional category.
3951
3962
  * Every value is derived from vds-output.json — no placeholder content.
3952
3963
  */
3964
+ /**
3965
+ * Single-pass JSX usage scan: returns a Set of component names (PascalCase-normalised)
3966
+ * that actually appear as <Tag in the project's source files (excluding ui/ definitions
3967
+ * and story files). This is used to distinguish "used in project" from "installed only".
3968
+ */
3969
+ function buildComponentUsageSet(componentNames, projectRoot) {
3970
+ // Pre-compute pascal names for each component
3971
+ const patterns = componentNames.map(name => {
3972
+ const pascal = name
3973
+ .replace(/[-\s]+(.)/g, (_, c) => c.toUpperCase())
3974
+ .replace(/^(.)/, c => c.toUpperCase());
3975
+ return { name, pascal };
3976
+ });
3977
+
3978
+ const usedNames = new Set();
3979
+
3980
+ // Resolve the shadcn ui directory path to exclude component definition files
3981
+ // (e.g. dialog.tsx contains <DialogClose> which would falsely register Dialog as "used")
3982
+ const uiDefDir = path.resolve(path.join(projectRoot, COMPONENTS_REL_DIR));
3983
+
3984
+ function walkDir(dir) {
3985
+ let entries;
3986
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
3987
+ for (const entry of entries) {
3988
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
3989
+ const full = path.join(dir, entry.name);
3990
+ if (entry.isDirectory()) {
3991
+ // Skip: story dirs, storybook config, AND the shadcn/ui component definitions dir
3992
+ if (["stories", ".storybook"].includes(entry.name)) continue;
3993
+ if (path.resolve(full) === uiDefDir) continue; // shadcn ui/ — skip definitions
3994
+ walkDir(full);
3995
+ } else if (/\.(tsx|jsx)$/.test(entry.name) && !/\.stories\./.test(entry.name)) {
3996
+ let src;
3997
+ try { src = fs.readFileSync(full, "utf-8"); } catch { continue; }
3998
+ for (const { name, pascal } of patterns) {
3999
+ if (!usedNames.has(name) && src.includes(`<${pascal}`)) {
4000
+ usedNames.add(name);
4001
+ }
4002
+ }
4003
+ }
4004
+ }
4005
+ }
4006
+
4007
+ // Walk common source roots (excluding the component definition directories themselves)
4008
+ const srcRoots = ["src", "client/src", "app", "pages"]
4009
+ .map(d => path.join(projectRoot, d))
4010
+ .filter(d => fs.existsSync(d));
4011
+ for (const root of srcRoots) walkDir(root);
4012
+
4013
+ return usedNames;
4014
+ }
4015
+
3953
4016
  function writeComponentInventoryStory(components, foundations) {
3954
4017
  if (!Array.isArray(components) || components.length === 0) return;
3955
4018
  const foundationsDir = path.join(STORIES_DIR, "foundations");
@@ -3963,6 +4026,10 @@ function writeComponentInventoryStory(components, foundations) {
3963
4026
  const categorized = {}; // category → [{ name, group, tokenCount, propCount, colorSwatches }]
3964
4027
  const foundColors = (foundations && foundations.colors) ? foundations.colors : {};
3965
4028
 
4029
+ // Determine which components are actually used as JSX in the project source
4030
+ const componentNames = components.map(c => c.name);
4031
+ const usedSet = buildComponentUsageSet(componentNames, PROJECT_ROOT);
4032
+
3966
4033
  for (const comp of components) {
3967
4034
  const g = comp.group || "Components";
3968
4035
  const gLower = g.toLowerCase();
@@ -3993,6 +4060,9 @@ function writeComponentInventoryStory(components, foundations) {
3993
4060
  // Visual preview HTML (computed at generation time using project's resolved colors)
3994
4061
  const previewHtml = buildInventoryPreviewHtml(comp.name, category, colorSwatches, tokens.filter(t => !/:/.test(t)));
3995
4062
 
4063
+ // Whether this component is actually used as a JSX tag anywhere in the project source
4064
+ const active = usedSet.has(comp.name);
4065
+
3996
4066
  if (!categorized[category]) categorized[category] = [];
3997
4067
  categorized[category].push({
3998
4068
  name: comp.name,
@@ -4001,6 +4071,7 @@ function writeComponentInventoryStory(components, foundations) {
4001
4071
  propCount: props.length,
4002
4072
  colorSwatches,
4003
4073
  previewHtml,
4074
+ active,
4004
4075
  });
4005
4076
  }
4006
4077
 
@@ -4015,92 +4086,106 @@ function writeComponentInventoryStory(components, foundations) {
4015
4086
 
4016
4087
  const inventoryData = sortedCategories.map(cat => ({
4017
4088
  category: cat,
4018
- components: categorized[cat].sort((a, b) => a.name.localeCompare(b.name)),
4089
+ // Sort: active first, then alphabetical within each group
4090
+ components: categorized[cat].sort((a, b) => {
4091
+ if (a.active !== b.active) return a.active ? -1 : 1;
4092
+ return a.name.localeCompare(b.name);
4093
+ }),
4019
4094
  }));
4020
4095
 
4021
4096
  const totalComponents = components.length;
4097
+ const activeComponents = [...usedSet].length;
4022
4098
  const uniqueTokens = [...new Set(
4023
4099
  components.flatMap(c => Array.isArray(c.tokens) ? c.tokens.filter(t => !/:/.test(t)) : [])
4024
4100
  )].length;
4025
4101
 
4026
4102
  const content = [
4103
+ `import React from "react";`,
4027
4104
  `import type { Meta, StoryObj } from "@storybook/react";`,
4028
4105
  ``,
4029
4106
  `const meta = { title: "Foundations/Component Inventory", parameters: { layout: "fullscreen" } } satisfies Meta;`,
4030
4107
  `export default meta;`,
4031
4108
  `type Story = StoryObj;`,
4032
4109
  ``,
4033
- `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; previewHtml: string }[] }[] = ${JSON.stringify(inventoryData)};`,
4110
+ `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; previewHtml: string; active: boolean }[] }[] = ${JSON.stringify(inventoryData)};`,
4034
4111
  `const totalComponents = ${totalComponents};`,
4112
+ `const activeComponents = ${activeComponents};`,
4035
4113
  `const uniqueTokens = ${uniqueTokens};`,
4036
4114
  ``,
4037
4115
  `export const Default: Story = {`,
4038
- ` render: () => (`,
4116
+ ` render: () => {`,
4117
+ ` const [showUnused, setShowUnused] = (window as any).__vdsShowUnused !== undefined`,
4118
+ ` ? [(window as any).__vdsShowUnused, (v: boolean) => { (window as any).__vdsShowUnused = v; }]`,
4119
+ ` : [false, () => {}];`,
4120
+ ` const [expanded, setExpanded] = React.useState(false);`,
4121
+ ` const Card = ({ comp }: { comp: any }) => (`,
4122
+ ` <div style={{ border: \`1px solid \${comp.active ? "#e5e7eb" : "#f0f0f0"}\`, borderRadius: 10, overflow: "hidden", background: comp.active ? "#fff" : "#fafafa", display: "flex", flexDirection: "column" as any, boxShadow: comp.active ? "0 1px 3px rgba(0,0,0,0.05)" : "none", opacity: comp.active ? 1 : 0.55 }}>`,
4123
+ ` <div style={{ background: comp.active ? "#f8f9fa" : "#f5f5f5", borderBottom: "1px solid #f0f0f0", minHeight: 64, display: "flex", alignItems: "center", justifyContent: "center" }}`,
4124
+ ` dangerouslySetInnerHTML={{ __html: comp.previewHtml }}`,
4125
+ ` />`,
4126
+ ` <div style={{ padding: "10px 12px" }}>`,
4127
+ ` <div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 5 }}>`,
4128
+ ` <span style={{ fontWeight: 700, fontSize: 13, color: comp.active ? "#111" : "#9ca3af" }}>{comp.name}</span>`,
4129
+ ` {comp.active && <span style={{ fontSize: 9, background: "#dcfce7", color: "#15803d", padding: "1px 5px", borderRadius: 4, fontWeight: 700, letterSpacing: "0.05em" }}>USED</span>}`,
4130
+ ` </div>`,
4131
+ ` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any }}>`,
4132
+ ` {comp.tokenCount > 0 && (`,
4133
+ ` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>{comp.tokenCount} tokens</span>`,
4134
+ ` )}`,
4135
+ ` {comp.colorSwatches.length > 0 && (`,
4136
+ ` <span style={{ fontSize: 10, background: "#fef9c3", color: "#713f12", padding: "2px 7px", borderRadius: 10, border: "1px solid #fde68a" }}>{comp.colorSwatches.length} colors</span>`,
4137
+ ` )}`,
4138
+ ` {!comp.active && (`,
4139
+ ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#9ca3af", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>installed</span>`,
4140
+ ` )}`,
4141
+ ` </div>`,
4142
+ ` </div>`,
4143
+ ` </div>`,
4144
+ ` );`,
4145
+ ` return (`,
4039
4146
  ` <div style={{ padding: 32, background: "#f8f9fa", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`,
4040
4147
  ` {/* Header */}`,
4041
4148
  ` <div style={{ marginBottom: 40 }}>`,
4042
4149
  ` <h2 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 8px" }}>Component Inventory</h2>`,
4043
- ` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>All components detected in this project visual style fingerprints derived from project tokens.</p>`,
4044
- ` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any }}>`,
4150
+ ` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>Components actually used in this project. <strong style={{ color: "#111" }}>USED</strong> = appears as a JSX tag in source files.</p>`,
4151
+ ` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any, marginBottom: 20 }}>`,
4045
4152
  ` {[`,
4046
- ` { value: totalComponents.toString(), label: "Components" },`,
4047
- ` { value: inventoryData.length.toString(), label: "Categories" },`,
4048
- ` { value: uniqueTokens.toString(), label: "Unique Tokens" },`,
4153
+ ` { value: activeComponents.toString(), label: "Used in Project", bg: "#f0fdf4", fg: "#15803d", border: "#bbf7d0" },`,
4154
+ ` { value: (totalComponents - activeComponents).toString(), label: "Installed Only", bg: "#f9fafb", fg: "#6b7280", border: "#e5e7eb" },`,
4155
+ ` { value: uniqueTokens.toString(), label: "Unique Tokens", bg: "#eff6ff", fg: "#1d4ed8", border: "#dbeafe" },`,
4049
4156
  ` ].map(stat => (`,
4050
- ` <div key={stat.label} style={{ padding: "12px 20px", background: "#fff", borderRadius: 10, textAlign: "center" as any, minWidth: 90, border: "1px solid #e5e7eb" }}>`,
4051
- ` <div style={{ fontSize: 26, fontWeight: 800, color: "#111", lineHeight: 1 }}>{stat.value}</div>`,
4052
- ` <div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>{stat.label}</div>`,
4157
+ ` <div key={stat.label} style={{ padding: "12px 20px", background: stat.bg, borderRadius: 10, textAlign: "center" as any, minWidth: 110, border: \`1px solid \${stat.border}\` }}>`,
4158
+ ` <div style={{ fontSize: 26, fontWeight: 800, color: stat.fg, lineHeight: 1 }}>{stat.value}</div>`,
4159
+ ` <div style={{ fontSize: 12, color: stat.fg, marginTop: 4, opacity: 0.8 }}>{stat.label}</div>`,
4053
4160
  ` </div>`,
4054
4161
  ` ))}`,
4055
4162
  ` </div>`,
4163
+ ` <button onClick={() => setExpanded(!expanded)} style={{ fontSize: 12, color: "#6b7280", background: "transparent", border: "1px solid #e5e7eb", borderRadius: 6, padding: "5px 12px", cursor: "pointer" }}>`,
4164
+ ` {expanded ? "Hide" : "Show"} installed-only components`,
4165
+ ` </button>`,
4056
4166
  ` </div>`,
4057
- ` {/* Category groups */}`,
4058
- ` {inventoryData.map(group => (`,
4167
+ ` {/* Category groups — active components first */}`,
4168
+ ` {inventoryData.map(group => {`,
4169
+ ` const active = group.components.filter((c: any) => c.active);`,
4170
+ ` const inactive = group.components.filter((c: any) => !c.active);`,
4171
+ ` if (active.length === 0 && !expanded) return null;`,
4172
+ ` return (`,
4059
4173
  ` <div key={group.category} style={{ marginBottom: 44 }}>`,
4060
4174
  ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #e5e7eb" }}>`,
4061
4175
  ` <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: "#111" }}>{group.category}</h3>`,
4062
- ` <span style={{ fontSize: 12, background: "#e0e7ff", color: "#3730a3", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{group.components.length}</span>`,
4176
+ ` <span style={{ fontSize: 12, background: "#dcfce7", color: "#15803d", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{active.length} used</span>`,
4177
+ ` {inactive.length > 0 && <span style={{ fontSize: 12, background: "#f3f4f6", color: "#9ca3af", padding: "2px 9px", borderRadius: 12 }}>{inactive.length} installed</span>}`,
4063
4178
  ` </div>`,
4064
4179
  ` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 12 }}>`,
4065
- ` {group.components.map(comp => (`,
4066
- ` <div key={comp.name} style={{ border: "1px solid #e5e7eb", borderRadius: 10, overflow: "hidden", background: "#fff", display: "flex", flexDirection: "column" as any, boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>`,
4067
- ` {/* Visual preview — rendered from project tokens at generation time */}`,
4068
- ` <div style={{ background: "#f8f9fa", borderBottom: "1px solid #f0f0f0", minHeight: 64, display: "flex", alignItems: "center", justifyContent: "center" }}`,
4069
- ` dangerouslySetInnerHTML={{ __html: comp.previewHtml }}`,
4070
- ` />`,
4071
- ` {/* Info strip */}`,
4072
- ` <div style={{ padding: "10px 12px" }}>`,
4073
- ` <div style={{ fontWeight: 700, fontSize: 13, color: "#111", marginBottom: 6 }}>{comp.name}</div>`,
4074
- ` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any }}>`,
4075
- ` {comp.tokenCount > 0 && (`,
4076
- ` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>`,
4077
- ` {comp.tokenCount} tokens`,
4078
- ` </span>`,
4079
- ` )}`,
4080
- ` {comp.colorSwatches.length > 0 && (`,
4081
- ` <span style={{ fontSize: 10, background: "#fef9c3", color: "#713f12", padding: "2px 7px", borderRadius: 10, border: "1px solid #fde68a" }}>`,
4082
- ` {comp.colorSwatches.length} colors`,
4083
- ` </span>`,
4084
- ` )}`,
4085
- ` {comp.propCount > 0 && (`,
4086
- ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#6b7280", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>`,
4087
- ` {comp.propCount} props`,
4088
- ` </span>`,
4089
- ` )}`,
4090
- ` {comp.group && comp.group !== "shadcn" && comp.group !== "UI" && comp.group !== "Components" && (`,
4091
- ` <span style={{ fontSize: 10, background: "#f0fdf4", color: "#166534", padding: "2px 7px", borderRadius: 10, border: "1px solid #bbf7d0" }}>`,
4092
- ` {comp.group}`,
4093
- ` </span>`,
4094
- ` )}`,
4095
- ` </div>`,
4096
- ` </div>`,
4097
- ` </div>`,
4098
- ` ))}`,
4180
+ ` {active.map((comp: any) => <Card key={comp.name} comp={comp} />)}`,
4181
+ ` {expanded && inactive.map((comp: any) => <Card key={comp.name} comp={comp} />)}`,
4099
4182
  ` </div>`,
4100
4183
  ` </div>`,
4101
- ` ))}`,
4184
+ ` );`,
4185
+ ` })}`,
4102
4186
  ` </div>`,
4103
- ` ),`,
4187
+ ` );`,
4188
+ ` },`,
4104
4189
  `};`,
4105
4190
  ].join("\n");
4106
4191
 
@@ -4154,6 +4239,7 @@ function writeChangelogStory(changelog) {
4154
4239
  const entries = Array.isArray(changelog) ? changelog.map((e) => ({ version: e.version, date: e.date, changes: e.changes || [] })) : [];
4155
4240
  const content =
4156
4241
  [
4242
+ "import React from \"react\";",
4157
4243
  "import type { Meta, StoryObj } from \"@storybook/react\";",
4158
4244
  "",
4159
4245
  "const meta = { title: \"Foundations/Changelog\", tags: [\"autodocs\"], parameters: { layout: \"fullscreen\" } } satisfies Meta;",
@@ -4473,21 +4559,32 @@ function main() {
4473
4559
  }
4474
4560
  }
4475
4561
 
4562
+ // Auto-detect which shadcn/ui components are actually used as JSX in the project source.
4563
+ // This replaces the manual `includeComponents` list — VDS now self-discovers which
4564
+ // shadcn primitives need stories instead of requiring users to maintain a whitelist.
4565
+ const allCompNames = components.map(c => c.name);
4566
+ const autoUsedSet = buildComponentUsageSet(allCompNames, PROJECT_ROOT);
4567
+ console.log(`[VDS] Auto-detected ${autoUsedSet.size} components with active JSX usage`);
4568
+
4476
4569
  let writtenCount = 0;
4477
4570
  for (const comp of components) {
4478
4571
  const componentName = toSafeComponentName(comp.name, comp.file);
4479
- // Skip unclassified and shadcn/ui primitives (UI group) — they're documented at ui.shadcn.com
4480
- // Only project-specific module groups (Circles, Finance, Projects, Time, …) get stories
4481
4572
  const g = comp.group || "Components";
4482
- // Skip shadcn/ui primitives by default — they're documented at ui.shadcn.com
4483
- // Users can override with vds.config.js: includeGroups: ["shadcn"] or includeComponents: ["Button", "Badge"]
4573
+ const gLower = g.toLowerCase();
4574
+
4575
+ // For shadcn/ui primitives: only generate a story if the component is actually
4576
+ // used as a JSX tag (<Button />, <Badge />, etc.) somewhere in the project source.
4577
+ // Manual overrides: includeGroups (all from group) and includeComponents (named list).
4484
4578
  const includeGroups = VDS_CONFIG.includeGroups || [];
4485
4579
  const includeComponents = VDS_CONFIG.includeComponents || [];
4486
- const gLower = g.toLowerCase();
4580
+
4487
4581
  if (g === "Uncategorized" || gLower === "ui" || gLower === "shadcn") {
4488
4582
  const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === gLower);
4489
4583
  const compIncluded = includeComponents.includes(componentName);
4490
- if (!groupIncluded && !compIncluded) {
4584
+ // Auto-detection: is the component used as a JSX tag in the project?
4585
+ const autoUsed = autoUsedSet.has(comp.name) || autoUsedSet.has(componentName);
4586
+
4587
+ if (!groupIncluded && !compIncluded && !autoUsed) {
4491
4588
  // Clean up leftover story files for components that are now in the skip group
4492
4589
  const oldStory = path.join(STORIES_DIR, `${componentName}.stories.tsx`);
4493
4590
  if (fs.existsSync(oldStory)) {
@@ -4495,12 +4592,15 @@ function main() {
4495
4592
  const firstLine = fs.readFileSync(oldStory, "utf-8").split("\n")[0] || "";
4496
4593
  if (firstLine.includes("@vds-regenerate")) {
4497
4594
  fs.unlinkSync(oldStory);
4498
- console.log(`[VDS] Removed shadcn/ui story: ${componentName}.stories.tsx`);
4595
+ console.log(`[VDS] Removed unused shadcn story: ${componentName}.stories.tsx`);
4499
4596
  }
4500
4597
  } catch { /* ignore */ }
4501
4598
  }
4502
4599
  continue;
4503
4600
  }
4601
+ if (autoUsed && !compIncluded) {
4602
+ console.log(`[VDS] Auto-including ${componentName} (active JSX usage detected)`);
4603
+ }
4504
4604
  }
4505
4605
  if (onlyName && componentName !== onlyName) continue;
4506
4606