vibe-design-system 2.8.65 → 2.8.66

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.66",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -3950,6 +3950,58 @@ function buildInventoryPreviewHtml(compName, category, colorSwatches, tokens) {
3950
3950
  * Shows all project components classified by Atlassian-style functional category.
3951
3951
  * Every value is derived from vds-output.json — no placeholder content.
3952
3952
  */
3953
+ /**
3954
+ * Single-pass JSX usage scan: returns a Set of component names (PascalCase-normalised)
3955
+ * that actually appear as <Tag in the project's source files (excluding ui/ definitions
3956
+ * and story files). This is used to distinguish "used in project" from "installed only".
3957
+ */
3958
+ function buildComponentUsageSet(componentNames, projectRoot) {
3959
+ // Pre-compute pascal names for each component
3960
+ const patterns = componentNames.map(name => {
3961
+ const pascal = name
3962
+ .replace(/[-\s]+(.)/g, (_, c) => c.toUpperCase())
3963
+ .replace(/^(.)/, c => c.toUpperCase());
3964
+ return { name, pascal };
3965
+ });
3966
+
3967
+ const usedNames = new Set();
3968
+
3969
+ // Resolve the shadcn ui directory path to exclude component definition files
3970
+ // (e.g. dialog.tsx contains <DialogClose> which would falsely register Dialog as "used")
3971
+ const uiDefDir = path.resolve(path.join(projectRoot, COMPONENTS_REL_DIR));
3972
+
3973
+ function walkDir(dir) {
3974
+ let entries;
3975
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
3976
+ for (const entry of entries) {
3977
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
3978
+ const full = path.join(dir, entry.name);
3979
+ if (entry.isDirectory()) {
3980
+ // Skip: story dirs, storybook config, AND the shadcn/ui component definitions dir
3981
+ if (["stories", ".storybook"].includes(entry.name)) continue;
3982
+ if (path.resolve(full) === uiDefDir) continue; // shadcn ui/ — skip definitions
3983
+ walkDir(full);
3984
+ } else if (/\.(tsx|jsx)$/.test(entry.name) && !/\.stories\./.test(entry.name)) {
3985
+ let src;
3986
+ try { src = fs.readFileSync(full, "utf-8"); } catch { continue; }
3987
+ for (const { name, pascal } of patterns) {
3988
+ if (!usedNames.has(name) && src.includes(`<${pascal}`)) {
3989
+ usedNames.add(name);
3990
+ }
3991
+ }
3992
+ }
3993
+ }
3994
+ }
3995
+
3996
+ // Walk common source roots (excluding the component definition directories themselves)
3997
+ const srcRoots = ["src", "client/src", "app", "pages"]
3998
+ .map(d => path.join(projectRoot, d))
3999
+ .filter(d => fs.existsSync(d));
4000
+ for (const root of srcRoots) walkDir(root);
4001
+
4002
+ return usedNames;
4003
+ }
4004
+
3953
4005
  function writeComponentInventoryStory(components, foundations) {
3954
4006
  if (!Array.isArray(components) || components.length === 0) return;
3955
4007
  const foundationsDir = path.join(STORIES_DIR, "foundations");
@@ -3963,6 +4015,10 @@ function writeComponentInventoryStory(components, foundations) {
3963
4015
  const categorized = {}; // category → [{ name, group, tokenCount, propCount, colorSwatches }]
3964
4016
  const foundColors = (foundations && foundations.colors) ? foundations.colors : {};
3965
4017
 
4018
+ // Determine which components are actually used as JSX in the project source
4019
+ const componentNames = components.map(c => c.name);
4020
+ const usedSet = buildComponentUsageSet(componentNames, PROJECT_ROOT);
4021
+
3966
4022
  for (const comp of components) {
3967
4023
  const g = comp.group || "Components";
3968
4024
  const gLower = g.toLowerCase();
@@ -3993,6 +4049,9 @@ function writeComponentInventoryStory(components, foundations) {
3993
4049
  // Visual preview HTML (computed at generation time using project's resolved colors)
3994
4050
  const previewHtml = buildInventoryPreviewHtml(comp.name, category, colorSwatches, tokens.filter(t => !/:/.test(t)));
3995
4051
 
4052
+ // Whether this component is actually used as a JSX tag anywhere in the project source
4053
+ const active = usedSet.has(comp.name);
4054
+
3996
4055
  if (!categorized[category]) categorized[category] = [];
3997
4056
  categorized[category].push({
3998
4057
  name: comp.name,
@@ -4001,6 +4060,7 @@ function writeComponentInventoryStory(components, foundations) {
4001
4060
  propCount: props.length,
4002
4061
  colorSwatches,
4003
4062
  previewHtml,
4063
+ active,
4004
4064
  });
4005
4065
  }
4006
4066
 
@@ -4015,10 +4075,15 @@ function writeComponentInventoryStory(components, foundations) {
4015
4075
 
4016
4076
  const inventoryData = sortedCategories.map(cat => ({
4017
4077
  category: cat,
4018
- components: categorized[cat].sort((a, b) => a.name.localeCompare(b.name)),
4078
+ // Sort: active first, then alphabetical within each group
4079
+ components: categorized[cat].sort((a, b) => {
4080
+ if (a.active !== b.active) return a.active ? -1 : 1;
4081
+ return a.name.localeCompare(b.name);
4082
+ }),
4019
4083
  }));
4020
4084
 
4021
4085
  const totalComponents = components.length;
4086
+ const activeComponents = [...usedSet].length;
4022
4087
  const uniqueTokens = [...new Set(
4023
4088
  components.flatMap(c => Array.isArray(c.tokens) ? c.tokens.filter(t => !/:/.test(t)) : [])
4024
4089
  )].length;
@@ -4030,77 +4095,85 @@ function writeComponentInventoryStory(components, foundations) {
4030
4095
  `export default meta;`,
4031
4096
  `type Story = StoryObj;`,
4032
4097
  ``,
4033
- `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; previewHtml: string }[] }[] = ${JSON.stringify(inventoryData)};`,
4098
+ `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
4099
  `const totalComponents = ${totalComponents};`,
4100
+ `const activeComponents = ${activeComponents};`,
4035
4101
  `const uniqueTokens = ${uniqueTokens};`,
4036
4102
  ``,
4037
4103
  `export const Default: Story = {`,
4038
- ` render: () => (`,
4104
+ ` render: () => {`,
4105
+ ` const [showUnused, setShowUnused] = (window as any).__vdsShowUnused !== undefined`,
4106
+ ` ? [(window as any).__vdsShowUnused, (v: boolean) => { (window as any).__vdsShowUnused = v; }]`,
4107
+ ` : [false, () => {}];`,
4108
+ ` const [expanded, setExpanded] = React.useState(false);`,
4109
+ ` const Card = ({ comp }: { comp: any }) => (`,
4110
+ ` <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 }}>`,
4111
+ ` <div style={{ background: comp.active ? "#f8f9fa" : "#f5f5f5", borderBottom: "1px solid #f0f0f0", minHeight: 64, display: "flex", alignItems: "center", justifyContent: "center" }}`,
4112
+ ` dangerouslySetInnerHTML={{ __html: comp.previewHtml }}`,
4113
+ ` />`,
4114
+ ` <div style={{ padding: "10px 12px" }}>`,
4115
+ ` <div style={{ display: "flex", alignItems: "center", gap: 5, marginBottom: 5 }}>`,
4116
+ ` <span style={{ fontWeight: 700, fontSize: 13, color: comp.active ? "#111" : "#9ca3af" }}>{comp.name}</span>`,
4117
+ ` {comp.active && <span style={{ fontSize: 9, background: "#dcfce7", color: "#15803d", padding: "1px 5px", borderRadius: 4, fontWeight: 700, letterSpacing: "0.05em" }}>USED</span>}`,
4118
+ ` </div>`,
4119
+ ` <div style={{ display: "flex", gap: 4, flexWrap: "wrap" as any }}>`,
4120
+ ` {comp.tokenCount > 0 && (`,
4121
+ ` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>{comp.tokenCount} tokens</span>`,
4122
+ ` )}`,
4123
+ ` {comp.colorSwatches.length > 0 && (`,
4124
+ ` <span style={{ fontSize: 10, background: "#fef9c3", color: "#713f12", padding: "2px 7px", borderRadius: 10, border: "1px solid #fde68a" }}>{comp.colorSwatches.length} colors</span>`,
4125
+ ` )}`,
4126
+ ` {!comp.active && (`,
4127
+ ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#9ca3af", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>installed</span>`,
4128
+ ` )}`,
4129
+ ` </div>`,
4130
+ ` </div>`,
4131
+ ` </div>`,
4132
+ ` );`,
4133
+ ` return (`,
4039
4134
  ` <div style={{ padding: 32, background: "#f8f9fa", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`,
4040
4135
  ` {/* Header */}`,
4041
4136
  ` <div style={{ marginBottom: 40 }}>`,
4042
4137
  ` <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 }}>`,
4138
+ ` <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>`,
4139
+ ` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any, marginBottom: 20 }}>`,
4045
4140
  ` {[`,
4046
- ` { value: totalComponents.toString(), label: "Components" },`,
4047
- ` { value: inventoryData.length.toString(), label: "Categories" },`,
4048
- ` { value: uniqueTokens.toString(), label: "Unique Tokens" },`,
4141
+ ` { value: activeComponents.toString(), label: "Used in Project", bg: "#f0fdf4", fg: "#15803d", border: "#bbf7d0" },`,
4142
+ ` { value: (totalComponents - activeComponents).toString(), label: "Installed Only", bg: "#f9fafb", fg: "#6b7280", border: "#e5e7eb" },`,
4143
+ ` { value: uniqueTokens.toString(), label: "Unique Tokens", bg: "#eff6ff", fg: "#1d4ed8", border: "#dbeafe" },`,
4049
4144
  ` ].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>`,
4145
+ ` <div key={stat.label} style={{ padding: "12px 20px", background: stat.bg, borderRadius: 10, textAlign: "center" as any, minWidth: 110, border: \`1px solid \${stat.border}\` }}>`,
4146
+ ` <div style={{ fontSize: 26, fontWeight: 800, color: stat.fg, lineHeight: 1 }}>{stat.value}</div>`,
4147
+ ` <div style={{ fontSize: 12, color: stat.fg, marginTop: 4, opacity: 0.8 }}>{stat.label}</div>`,
4053
4148
  ` </div>`,
4054
4149
  ` ))}`,
4055
4150
  ` </div>`,
4151
+ ` <button onClick={() => setExpanded(!expanded)} style={{ fontSize: 12, color: "#6b7280", background: "transparent", border: "1px solid #e5e7eb", borderRadius: 6, padding: "5px 12px", cursor: "pointer" }}>`,
4152
+ ` {expanded ? "Hide" : "Show"} installed-only components`,
4153
+ ` </button>`,
4056
4154
  ` </div>`,
4057
- ` {/* Category groups */}`,
4058
- ` {inventoryData.map(group => (`,
4155
+ ` {/* Category groups — active components first */}`,
4156
+ ` {inventoryData.map(group => {`,
4157
+ ` const active = group.components.filter((c: any) => c.active);`,
4158
+ ` const inactive = group.components.filter((c: any) => !c.active);`,
4159
+ ` if (active.length === 0 && !expanded) return null;`,
4160
+ ` return (`,
4059
4161
  ` <div key={group.category} style={{ marginBottom: 44 }}>`,
4060
4162
  ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #e5e7eb" }}>`,
4061
4163
  ` <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>`,
4164
+ ` <span style={{ fontSize: 12, background: "#dcfce7", color: "#15803d", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{active.length} used</span>`,
4165
+ ` {inactive.length > 0 && <span style={{ fontSize: 12, background: "#f3f4f6", color: "#9ca3af", padding: "2px 9px", borderRadius: 12 }}>{inactive.length} installed</span>}`,
4063
4166
  ` </div>`,
4064
4167
  ` <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
- ` ))}`,
4168
+ ` {active.map((comp: any) => <Card key={comp.name} comp={comp} />)}`,
4169
+ ` {expanded && inactive.map((comp: any) => <Card key={comp.name} comp={comp} />)}`,
4099
4170
  ` </div>`,
4100
4171
  ` </div>`,
4101
- ` ))}`,
4172
+ ` );`,
4173
+ ` })}`,
4102
4174
  ` </div>`,
4103
- ` ),`,
4175
+ ` );`,
4176
+ ` },`,
4104
4177
  `};`,
4105
4178
  ].join("\n");
4106
4179
 
@@ -4473,21 +4546,32 @@ function main() {
4473
4546
  }
4474
4547
  }
4475
4548
 
4549
+ // Auto-detect which shadcn/ui components are actually used as JSX in the project source.
4550
+ // This replaces the manual `includeComponents` list — VDS now self-discovers which
4551
+ // shadcn primitives need stories instead of requiring users to maintain a whitelist.
4552
+ const allCompNames = components.map(c => c.name);
4553
+ const autoUsedSet = buildComponentUsageSet(allCompNames, PROJECT_ROOT);
4554
+ console.log(`[VDS] Auto-detected ${autoUsedSet.size} components with active JSX usage`);
4555
+
4476
4556
  let writtenCount = 0;
4477
4557
  for (const comp of components) {
4478
4558
  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
4559
  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"]
4560
+ const gLower = g.toLowerCase();
4561
+
4562
+ // For shadcn/ui primitives: only generate a story if the component is actually
4563
+ // used as a JSX tag (<Button />, <Badge />, etc.) somewhere in the project source.
4564
+ // Manual overrides: includeGroups (all from group) and includeComponents (named list).
4484
4565
  const includeGroups = VDS_CONFIG.includeGroups || [];
4485
4566
  const includeComponents = VDS_CONFIG.includeComponents || [];
4486
- const gLower = g.toLowerCase();
4567
+
4487
4568
  if (g === "Uncategorized" || gLower === "ui" || gLower === "shadcn") {
4488
4569
  const groupIncluded = includeGroups.some((ig) => ig.toLowerCase() === gLower);
4489
4570
  const compIncluded = includeComponents.includes(componentName);
4490
- if (!groupIncluded && !compIncluded) {
4571
+ // Auto-detection: is the component used as a JSX tag in the project?
4572
+ const autoUsed = autoUsedSet.has(comp.name) || autoUsedSet.has(componentName);
4573
+
4574
+ if (!groupIncluded && !compIncluded && !autoUsed) {
4491
4575
  // Clean up leftover story files for components that are now in the skip group
4492
4576
  const oldStory = path.join(STORIES_DIR, `${componentName}.stories.tsx`);
4493
4577
  if (fs.existsSync(oldStory)) {
@@ -4495,12 +4579,15 @@ function main() {
4495
4579
  const firstLine = fs.readFileSync(oldStory, "utf-8").split("\n")[0] || "";
4496
4580
  if (firstLine.includes("@vds-regenerate")) {
4497
4581
  fs.unlinkSync(oldStory);
4498
- console.log(`[VDS] Removed shadcn/ui story: ${componentName}.stories.tsx`);
4582
+ console.log(`[VDS] Removed unused shadcn story: ${componentName}.stories.tsx`);
4499
4583
  }
4500
4584
  } catch { /* ignore */ }
4501
4585
  }
4502
4586
  continue;
4503
4587
  }
4588
+ if (autoUsed && !compIncluded) {
4589
+ console.log(`[VDS] Auto-including ${componentName} (active JSX usage detected)`);
4590
+ }
4504
4591
  }
4505
4592
  if (onlyName && componentName !== onlyName) continue;
4506
4593