vibe-design-system 2.8.63 → 2.8.65

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.63",
3
+ "version": "2.8.65",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -1991,6 +1991,44 @@ function buildStoryFileContent(comp) {
1991
1991
  return buildRecipeStoryContent(comp, componentName, importPath, title, source, exportStyle, RECIPES[componentName], defaultArgLines, lucideImports, iconPropNames, needReact, variantMap);
1992
1992
  }
1993
1993
 
1994
+ // Detect if the component uses @tanstack/react-query hooks → needs QueryClientProvider in story.
1995
+ // Checks direct imports AND one level deep into local hook files (e.g. @/hooks/use-crypto.ts).
1996
+ const needsQueryClient = (() => {
1997
+ if (!source.length) return false;
1998
+ // Direct usage in this file
1999
+ if (/from\s+['"]@tanstack\/react-query['"]/m.test(source) ||
2000
+ /\b(useQuery|useMutation|useQueryClient|useInfiniteQuery|useSuspenseQuery)\b/.test(source)) {
2001
+ return true;
2002
+ }
2003
+ // One level deep: check imported hook files (e.g. @/hooks/*, ../hooks/*, ./hooks/*)
2004
+ // Derive the @/ alias source root from COMPONENTS_REL_DIR:
2005
+ // "src/components" → srcRoot = "src"
2006
+ // "client/src/components" → srcRoot = "client/src"
2007
+ const atAliasRoot = path.dirname(COMPONENTS_REL_DIR); // e.g. "src" or "client/src"
2008
+ const hookImportRe = /from\s+['"]([^'"]*\/hooks\/[^'"]+)['"]/g;
2009
+ let m;
2010
+ while ((m = hookImportRe.exec(source)) !== null) {
2011
+ try {
2012
+ let rel = m[1];
2013
+ // Resolve @ alias using derived source root
2014
+ if (rel.startsWith("@/")) rel = atAliasRoot + "/" + rel.slice(2);
2015
+ const exts = [".ts", ".tsx", ".js", ".jsx"];
2016
+ for (const ext of exts) {
2017
+ const full = path.join(PROJECT_ROOT, rel + ext);
2018
+ if (fs.existsSync(full)) {
2019
+ const hookSrc = fs.readFileSync(full, "utf-8");
2020
+ if (/from\s+['"]@tanstack\/react-query['"]/m.test(hookSrc) ||
2021
+ /\b(useQuery|useMutation|useQueryClient|useInfiniteQuery)\b/.test(hookSrc)) {
2022
+ return true;
2023
+ }
2024
+ break;
2025
+ }
2026
+ }
2027
+ } catch { /* ignore */ }
2028
+ }
2029
+ return false;
2030
+ })();
2031
+
1994
2032
  const lines = [];
1995
2033
  lines.push(`// @vds-regenerate — VDS auto-generated. Remove this line to prevent overwrite.`);
1996
2034
  lines.push(`import type { Meta, StoryObj } from "@storybook/react";`);
@@ -1998,11 +2036,17 @@ function buildStoryFileContent(comp) {
1998
2036
  if (lucideImports.length > 0) {
1999
2037
  lines.push(`import { ${lucideImports.join(", ")} } from "lucide-react";`);
2000
2038
  }
2039
+ if (needsQueryClient) {
2040
+ lines.push(`import { QueryClient, QueryClientProvider } from "@tanstack/react-query";`);
2041
+ }
2001
2042
 
2002
2043
  // Dynamic import + catch so "Failed to fetch dynamically imported module" shows a fallback instead of breaking Storybook
2003
2044
  const loadFallback = `() => React.createElement('div', { style: { padding: 16, color: '#666', fontSize: 14 } }, 'Component could not be loaded (import error).')`;
2004
2045
  const getDefault = `(m) => ({ default: m["${componentName}"] ?? m.default })`;
2005
2046
  lines.push(`const ComponentRef = React.lazy(() => import(/* @vite-ignore */ "${importPath}").then(${getDefault}).catch(() => ({ default: ${loadFallback} })));`);
2047
+ if (needsQueryClient) {
2048
+ lines.push(`const _queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity } } });`);
2049
+ }
2006
2050
  lines.push("");
2007
2051
 
2008
2052
  const safeDefaultsObj = componentName && SAFE_WRAPPER_DEFAULTS[componentName];
@@ -2018,6 +2062,10 @@ function buildStoryFileContent(comp) {
2018
2062
  if (profile !== "SECTION") lines.push(` tags: ["autodocs"],`);
2019
2063
  // Center small components (VARIANT, WRAPPER, CONFIGURED, SAFE) to prevent vertical stretching
2020
2064
  if (profile !== "SECTION") lines.push(` parameters: { layout: "centered" },`);
2065
+ // Wrap with QueryClientProvider for components that use @tanstack/react-query hooks
2066
+ if (needsQueryClient) {
2067
+ lines.push(` decorators: [(Story: any) => React.createElement(QueryClientProvider, { client: _queryClient }, React.createElement(Story))],`);
2068
+ }
2021
2069
 
2022
2070
  // Build argTypes from extracted TypeScript props + icon-specific overrides
2023
2071
  const argTypeEntries = [];
@@ -2161,7 +2209,7 @@ function buildStoryFileContent(comp) {
2161
2209
  lines.push(` </section>`);
2162
2210
  lines.push(` );`);
2163
2211
  lines.push(` return (`);
2164
- lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh" }}>`);
2212
+ lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`);
2165
2213
  lines.push(` <h2 style={{ fontSize: 22, fontWeight: 700, margin: "0 0 6px" }}>Design Tokens</h2>`);
2166
2214
  lines.push(` <p style={{ fontSize: 13, color: "#6b7280", margin: "0 0 32px" }}>Tailwind utilities used in <code style={{ background: "#f3f4f6", padding: "1px 6px", borderRadius: 4, fontSize: 12 }}>${componentName}</code> — resolved to project values.</p>`);
2167
2215
  lines.push(` {colorTokens.length > 0 && section("Color", (`);
@@ -2230,7 +2278,7 @@ function buildStoryFileContent(comp) {
2230
2278
  lines.push(` <p style={{ margin: "0 0 12px", fontSize: 11, fontWeight: 700, color: "#6b7280", textTransform: "uppercase" as any, letterSpacing: "0.08em" }}>{label}</p>`);
2231
2279
  lines.push(` );`);
2232
2280
  lines.push(` return (`);
2233
- lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh" }}>`);
2281
+ lines.push(` <div style={{ padding: 40, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`);
2234
2282
  lines.push(` <div style={{ marginBottom: 32 }}>`);
2235
2283
  lines.push(` <h2 style={{ fontSize: 22, fontWeight: 700, margin: "0 0 6px" }}>Usage</h2>`);
2236
2284
  lines.push(` <p style={{ fontSize: 13, color: "#6b7280", margin: 0 }}>`);
@@ -2477,7 +2525,7 @@ function writeFoundationsStories(foundations) {
2477
2525
  "",
2478
2526
  "export const Default: Story = {",
2479
2527
  " render: () => (",
2480
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: 500, color: \"#111\" }}>",
2528
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
2481
2529
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\", color: \"#111\" }}>Colors</h2>",
2482
2530
  " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 40px\" }}>Design tokens defined in CSS variables — grouped by role</p>",
2483
2531
  "",
@@ -2596,7 +2644,7 @@ function writeFoundationsStories(foundations) {
2596
2644
  "",
2597
2645
  "export const Default: Story = {",
2598
2646
  " render: () => (",
2599
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, maxWidth: 860, color: \"#111\", background: \"#fff\", minHeight: \"100vh\" }}>",
2647
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, color: \"#111\", background: \"#fff\", minHeight: \"100vh\", width: \"100%\" }}>",
2600
2648
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Typography</h2>",
2601
2649
  " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 32px\" }}>Type scale, font families, and weights</p>",
2602
2650
  "",
@@ -2752,7 +2800,7 @@ function writeFoundationsStories(foundations) {
2752
2800
  "",
2753
2801
  "export const Default: Story = {",
2754
2802
  " render: () => (",
2755
- " <div style={{ display: \"flex\", gap: 24, flexWrap: \"wrap\", padding: 32, background: \"#fff\", minHeight: \"100vh\" }}>",
2803
+ " <div style={{ display: \"flex\", gap: 24, flexWrap: \"wrap\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\" }}>",
2756
2804
  " {assets.length === 0 ? <p style={{ color: '#888', fontFamily: 'monospace' }}>No brand assets found — add images to public/ or client/public/</p> : assets.map((a, i) => (",
2757
2805
  " <div key={i} style={{ textAlign: \"center\", background: '#f8f8f8', borderRadius: 8, padding: 12, minWidth: 120 }}>",
2758
2806
  " <img src={a.url || (\"/\" + String(a.path || \"\").replace(/^public\\//, \"\"))} style={{ maxHeight: 100, maxWidth: 180, objectFit: \"contain\", display: 'block', margin: '0 auto' }} alt={a.name || \"\"} />",
@@ -2849,7 +2897,7 @@ function writeFoundationsStories(foundations) {
2849
2897
  " ];",
2850
2898
  " const TILE_SIZES = [16, 20, 24];",
2851
2899
  " return (",
2852
- " <div style={{ padding: 32, fontFamily: \"system-ui,sans-serif\", background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
2900
+ " <div style={{ padding: 32, fontFamily: \"system-ui,sans-serif\", background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
2853
2901
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Icons</h2>",
2854
2902
  " <div style={{ marginBottom: 28 }}>",
2855
2903
  " <p style={{ margin: 0, marginBottom: 4, fontSize: 13, color: \"#6b7280\" }}>",
@@ -2992,7 +3040,7 @@ function writeFoundationsStories(foundations) {
2992
3040
  " );",
2993
3041
  " };",
2994
3042
  " return (",
2995
- " <div style={{ padding: 24, fontFamily: \"sans-serif\", maxWidth: 900, background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
3043
+ " <div style={{ padding: 24, fontFamily: \"sans-serif\", background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
2996
3044
  "",
2997
3045
  " {/* ── Breakpoints ── */}",
2998
3046
  " <section style={{ marginBottom: 40 }}>",
@@ -3148,7 +3196,7 @@ function writeFoundationsStories(foundations) {
3148
3196
  "",
3149
3197
  "export const Default: Story = {",
3150
3198
  " render: () => (",
3151
- " <div style={{ padding: 32, fontFamily: \"system-ui, sans-serif\", background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
3199
+ " <div style={{ padding: 32, fontFamily: \"system-ui, sans-serif\", background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
3152
3200
  " <h2 style={{ marginBottom: 8 }}>Used Button variants in app code</h2>",
3153
3201
  " <p style={{ marginBottom: 16, color: \"#888\", fontSize: 13 }}>Based on &lt;Button ... /&gt; usages in src/ (ignores lowercase &lt;button&gt;).</p>",
3154
3202
  " <table style={{ borderCollapse: \"collapse\", width: \"100%\", fontSize: 13 }}>",
@@ -3225,7 +3273,7 @@ function writeFoundationsStories(foundations) {
3225
3273
  "",
3226
3274
  "export const Default: Story = {",
3227
3275
  " render: () => (",
3228
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, maxWidth: 760, color: \"#111\", background: \"#fff\", minHeight: \"100vh\" }}>",
3276
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, color: \"#111\", background: \"#fff\", minHeight: \"100vh\", width: \"100%\" }}>",
3229
3277
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Spacing Scale</h2>",
3230
3278
  " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 24px\" }}>Base unit: 0.25rem = 4px · built on an 8px grid system</p>",
3231
3279
  " {usedSpacing.length > 0 && (",
@@ -3335,7 +3383,7 @@ function writeFoundationsStories(foundations) {
3335
3383
  "",
3336
3384
  "export const Default: Story = {",
3337
3385
  " render: () => (",
3338
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
3386
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
3339
3387
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\", color: \"#111\" }}>Elevation & Shadows</h2>",
3340
3388
  " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 32px\" }}>Depth is communicated through layered shadow levels</p>",
3341
3389
  " <h3 style={{ fontSize: 15, fontWeight: 600, margin: \"0 0 14px\", color: \"#374151\" }}>Shadow Scale</h3>",
@@ -3453,7 +3501,7 @@ function writeFoundationsStories(foundations) {
3453
3501
  "",
3454
3502
  "export const Default: Story = {",
3455
3503
  " render: () => (",
3456
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, maxWidth: 800, background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
3504
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
3457
3505
  " <h2 style={{ fontSize: 20, fontWeight: 700, margin: \"0 0 4px\" }}>Border & Radius</h2>",
3458
3506
  " <p style={{ fontSize: 13, color: \"#888\", margin: \"0 0 32px\" }}>Corner radius scale — from sharp edges to fully rounded</p>",
3459
3507
  " {usedRadius.length > 0 && (",
@@ -3584,7 +3632,7 @@ function writeFoundationsStories(foundations) {
3584
3632
  " );",
3585
3633
  "",
3586
3634
  " return (",
3587
- " <div style={{ padding: 40, fontFamily: \"system-ui,sans-serif\", background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
3635
+ " <div style={{ padding: 40, fontFamily: \"system-ui,sans-serif\", background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
3588
3636
  " <h2 style={{ fontSize: 22, fontWeight: 700, margin: \"0 0 4px\" }}>Motion & Interaction</h2>",
3589
3637
  " <p style={{ fontSize: 13, color: \"#6b7280\", margin: \"0 0 40px\" }}>Consistent timing and easing for smooth, intentional motion.</p>",
3590
3638
  "",
@@ -3753,7 +3801,7 @@ function writeComponentSuggestionsStory(componentSuggestions) {
3753
3801
  "",
3754
3802
  "export const Default: Story = {",
3755
3803
  " render: () => (",
3756
- " <div style={{ padding: 32, fontFamily: 'system-ui, sans-serif', background: '#fff', minHeight: '100vh', color: '#111' }}>",
3804
+ " <div style={{ padding: 32, fontFamily: 'system-ui, sans-serif', background: '#fff', minHeight: '100vh', width: '100%', color: '#111' }}>",
3757
3805
  " <h2 style={{ marginBottom: 8, fontSize: 24, fontWeight: 700, color: '#1a202c' }}>Component Suggestions</h2>",
3758
3806
  " <p style={{ color: '#718096', marginBottom: 32, fontSize: 14 }}>Bu pattern'ler birden fazla dosyada tekrar ediyor. Her biri ayrı bir component olabilir. <strong>Copy Prompt</strong> butonuna tıklayıp Cursor veya Superflex'e yapıştır.</p>",
3759
3807
  " <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>",
@@ -3767,6 +3815,136 @@ function writeComponentSuggestionsStory(componentSuggestions) {
3767
3815
  console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentSuggestions.stories.tsx")));
3768
3816
  }
3769
3817
 
3818
+ /**
3819
+ * Determines the visual preview shape and colors for a component in the inventory.
3820
+ * Returns an HTML string rendered at generation time using the project's resolved colors.
3821
+ */
3822
+ function buildInventoryPreviewHtml(compName, category, colorSwatches, tokens) {
3823
+ const n = compName.toLowerCase().replace(/[\s-]+/g, "");
3824
+
3825
+ // Extract colors from resolved swatches
3826
+ const bgSwatch = colorSwatches.find(s => /^bg-(?!gradient)/.test(s.token));
3827
+ const textSwatch = colorSwatches.find(s => /^text-/.test(s.token));
3828
+ const brdSwatch = colorSwatches.find(s => /^border-/.test(s.token));
3829
+ const accSwatch = colorSwatches.find(s => s.token === "bg-primary" || s.token === "border-primary" || s.token === "ring-primary");
3830
+
3831
+ // Fallback neutral palette (works on white canvas)
3832
+ const bg = bgSwatch?.hex || "#f3f4f6";
3833
+ const txt = textSwatch?.hex || "#374151";
3834
+ const brd = brdSwatch?.hex || "#e5e7eb";
3835
+ const acc = accSwatch?.hex || "#6366f1";
3836
+ // Lighten/darken helpers (inline, no deps)
3837
+ const hex2 = (h) => { // slightly lighter bg for header strips
3838
+ const n = parseInt(h.slice(1), 16);
3839
+ const r = Math.min(255, (n >> 16) + 30), g = Math.min(255, ((n >> 8) & 0xff) + 30), b = Math.min(255, (n & 0xff) + 30);
3840
+ return "#" + [r,g,b].map(x => x.toString(16).padStart(2,"0")).join("");
3841
+ };
3842
+
3843
+ // Border-radius from tokens
3844
+ const radTok = tokens.find(t => /^rounded/.test(t));
3845
+ const rad = radTok === "rounded-none" ? 0 : radTok === "rounded-sm" ? 2 : radTok === "rounded" ? 4
3846
+ : radTok === "rounded-md" ? 6 : radTok === "rounded-lg" ? 8 : radTok === "rounded-xl" ? 12
3847
+ : radTok === "rounded-2xl" ? 16 : radTok === "rounded-3xl" ? 24 : radTok === "rounded-full" ? 9999 : 4;
3848
+
3849
+ const wrap = (inner) => `<div style="width:100%;height:56px;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:0 10px;box-sizing:border-box">${inner}</div>`;
3850
+
3851
+ // ── Specific component shapes ────────────────────────────────────────────
3852
+ if (/^input$/.test(n) || (category === "Forms and Input" && /input/.test(n))) {
3853
+ return wrap(`<div style="width:100%;max-width:200px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;display:flex;align-items:center;gap:6px;box-sizing:border-box"><div style="flex:1;height:8px;background:${txt};opacity:0.3;border-radius:2px"></div></div>`);
3854
+ }
3855
+ if (/^textarea$/.test(n)) {
3856
+ return wrap(`<div style="width:100%;max-width:200px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;height:38px;box-sizing:border-box;display:flex;flex-direction:column;gap:4px"><div style="height:6px;background:${txt};opacity:0.3;border-radius:2px;width:80%"></div><div style="height:6px;background:${txt};opacity:0.2;border-radius:2px;width:50%"></div></div>`);
3857
+ }
3858
+ if (/^(button|submitbutton)$/.test(n) || (category === "Forms and Input" && /^button$/.test(n))) {
3859
+ return wrap(`<div style="display:inline-flex;align-items:center;background:${acc};color:#fff;border:1px solid ${acc};border-radius:${rad}px;padding:6px 18px;font-size:11px;font-weight:600;white-space:nowrap">${compName}</div>`);
3860
+ }
3861
+ if (/^toggle$/.test(n)) {
3862
+ return wrap(`<div style="display:flex;align-items:center;gap:6px"><div style="display:inline-flex;align-items:center;background:${acc};color:#fff;border-radius:${rad}px;padding:5px 12px;font-size:11px;font-weight:600">ON</div><div style="display:inline-flex;align-items:center;background:${bg};color:${txt};border:1px solid ${brd};border-radius:${rad}px;padding:5px 12px;font-size:11px">OFF</div></div>`);
3863
+ }
3864
+ if (/^(badge|chip|tag|lozenge|pill)$/.test(n) || category === "Status Indicators") {
3865
+ const bBg = acc + "20"; const bTxt = acc;
3866
+ return wrap(`<div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:center"><span style="background:${bBg};color:${bTxt};border:1px solid ${brd};border-radius:${Math.max(rad,20)}px;padding:3px 10px;font-size:11px;font-weight:600">${compName}</span><span style="background:${brd}40;color:${txt};border:1px solid ${brd};border-radius:${Math.max(rad,20)}px;padding:3px 10px;font-size:11px">Default</span></div>`);
3867
+ }
3868
+ if (/^(checkbox|radio|radiogroup)$/.test(n)) {
3869
+ return wrap(`<div style="display:flex;flex-direction:column;gap:5px"><div style="display:flex;align-items:center;gap:6px"><div style="width:14px;height:14px;border-radius:3px;background:${acc};flex-shrink:0"></div><div style="height:6px;background:${txt};opacity:0.35;border-radius:2px;width:50px"></div></div><div style="display:flex;align-items:center;gap:6px"><div style="width:14px;height:14px;border-radius:3px;background:${bg};border:2px solid ${brd};flex-shrink:0"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:40px"></div></div></div>`);
3870
+ }
3871
+ if (/^switch$/.test(n)) {
3872
+ return wrap(`<div style="display:flex;align-items:center;gap:12px"><div style="width:36px;height:20px;background:${acc};border-radius:9999px;position:relative;flex-shrink:0"><div style="position:absolute;right:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:9999px"></div></div><div style="width:36px;height:20px;background:${brd};border-radius:9999px;position:relative;flex-shrink:0"><div style="position:absolute;left:2px;top:2px;width:16px;height:16px;background:#fff;border-radius:9999px"></div></div></div>`);
3873
+ }
3874
+ if (/^(select|combobox)$/.test(n)) {
3875
+ return wrap(`<div style="width:100%;max-width:190px;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px 10px;display:flex;align-items:center;gap:4px;box-sizing:border-box"><div style="flex:1;height:8px;background:${txt};opacity:0.3;border-radius:2px"></div><div style="font-size:8px;color:${txt};opacity:0.5;flex-shrink:0">▾</div></div>`);
3876
+ }
3877
+ if (/^slider$/.test(n)) {
3878
+ return wrap(`<div style="width:80%;position:relative;padding:8px 0"><div style="height:4px;background:${brd};border-radius:9999px;position:relative"><div style="position:absolute;left:0;top:0;height:100%;width:60%;background:${acc};border-radius:9999px"></div><div style="position:absolute;left:calc(60% - 7px);top:50%;transform:translateY(-50%);width:14px;height:14px;background:${bg};border:2px solid ${acc};border-radius:9999px"></div></div></div>`);
3879
+ }
3880
+ if (/^skeleton$/.test(n) || category === "Loading") {
3881
+ return wrap(`<div style="display:flex;flex-direction:column;gap:5px;width:80%"><div style="height:10px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:100%"></div><div style="height:8px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:80%;opacity:0.7"></div><div style="height:8px;background:${bg === "#f3f4f6" ? "#e5e7eb" : hex2(bg)};border-radius:${rad}px;width:60%;opacity:0.5"></div></div>`);
3882
+ }
3883
+ if (/^(spinner|loader)$/.test(n)) {
3884
+ return wrap(`<div style="width:24px;height:24px;border-radius:9999px;border:3px solid ${brd};border-top-color:${acc};"></div>`);
3885
+ }
3886
+ if (/^(alert|banner|notification)$/.test(n) || category === "Messaging") {
3887
+ return wrap(`<div style="display:flex;align-items:center;gap:8px;background:${acc}15;border:1px solid ${acc}40;border-left:3px solid ${acc};border-radius:${rad}px;padding:6px 10px;width:100%;max-width:220px;box-sizing:border-box"><div style="flex:1;height:7px;background:${txt};opacity:0.3;border-radius:2px"></div></div>`);
3888
+ }
3889
+ if (/^toast$/.test(n)) {
3890
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:8px 12px;width:100%;max-width:210px;box-sizing:border-box;display:flex;justify-content:space-between;align-items:center"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:60%"></div><div style="font-size:9px;color:${txt};opacity:0.4">✕</div></div>`);
3891
+ }
3892
+ if (/^avatar$/.test(n) || /^(images and icons)$/i.test(category)) {
3893
+ return wrap(`<div style="display:flex;gap:8px;align-items:center"><div style="width:36px;height:36px;border-radius:9999px;background:${acc};display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;color:#fff;flex-shrink:0">${compName.slice(0,1).toUpperCase()}</div><div style="width:28px;height:28px;border-radius:9999px;background:${bg};border:1px solid ${brd};flex-shrink:0"></div></div>`);
3894
+ }
3895
+ if (/^(card|tile|panel)$/.test(n) || (category === "Layout and Structure" && /^card$/.test(n))) {
3896
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:10px 12px;width:85%;box-sizing:border-box"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:50%;margin-bottom:5px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%;margin-bottom:4px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:65%"></div></div>`);
3897
+ }
3898
+ if (/^(accordion|collapsible)$/.test(n)) {
3899
+ return wrap(`<div style="width:85%;box-sizing:border-box"><div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:7px 12px;display:flex;justify-content:space-between;align-items:center"><div style="height:7px;background:${txt};opacity:0.4;border-radius:2px;width:50%"></div><div style="font-size:10px;color:${txt};opacity:0.5">▾</div></div></div>`);
3900
+ }
3901
+ if (/^(dialog|modal|sheet|alertdialog)$/.test(n) || category === "Overlays and Layering") {
3902
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:85%;box-sizing:border-box"><div style="background:${hex2(bg)};border-bottom:1px solid ${brd};padding:5px 10px;display:flex;justify-content:space-between;align-items:center"><div style="height:6px;background:${txt};opacity:0.5;border-radius:2px;width:50px"></div><div style="font-size:9px;color:${txt};opacity:0.3">✕</div></div><div style="padding:8px 10px;display:flex;flex-direction:column;gap:3px"><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%"></div><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:55%"></div></div></div>`);
3903
+ }
3904
+ if (/^drawer$/.test(n)) {
3905
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px ${rad}px 0 0;width:85%;box-sizing:border-box"><div style="padding:8px 12px;border-bottom:1px solid ${brd};display:flex;align-items:center;gap:6px"><div style="height:6px;background:${txt};opacity:0.4;border-radius:2px;width:60%"></div></div><div style="padding:8px 12px;display:flex;flex-direction:column;gap:3px"><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:100%"></div></div></div>`);
3906
+ }
3907
+ if (/^(tabs|tab)$/.test(n)) {
3908
+ return wrap(`<div style="width:85%;box-sizing:border-box"><div style="display:flex;background:${bg};border-radius:${rad}px;padding:3px;gap:2px"><div style="flex:1;background:${bg === "#f3f4f6" ? "#fff" : hex2(bg)};border-radius:${Math.max(rad-2,2)}px;padding:4px 0;font-size:10px;font-weight:600;color:${txt};text-align:center">Tab 1</div><div style="flex:1;padding:4px 0;font-size:10px;color:${txt};opacity:0.5;text-align:center">Tab 2</div><div style="flex:1;padding:4px 0;font-size:10px;color:${txt};opacity:0.5;text-align:center">Tab 3</div></div></div>`);
3909
+ }
3910
+ if (/^(table|datatable)$/.test(n) || category === "Text and Data Display") {
3911
+ return wrap(`<div style="border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:85%;box-sizing:border-box"><div style="display:flex;background:${hex2(bg)};border-bottom:1px solid ${brd};padding:4px 8px;gap:8px">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.3;border-radius:1px"></div>`).join("")}</div><div style="display:flex;background:${bg};padding:4px 8px;gap:8px">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.15;border-radius:1px"></div>`).join("")}</div><div style="display:flex;background:${bg};padding:4px 8px;gap:8px;opacity:0.7">${[1,1,1].map(()=>`<div style="flex:1;height:5px;background:${txt};opacity:0.15;border-radius:1px"></div>`).join("")}</div></div>`);
3912
+ }
3913
+ if (/^(sidebar|appsidebar)$/.test(n)) {
3914
+ return wrap(`<div style="display:flex;gap:6px;align-items:flex-start"><div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;width:50px;display:flex;flex-direction:column;gap:4px;padding:6px 4px"><div style="height:4px;background:${acc};border-radius:2px;width:80%;margin:0 auto"></div><div style="height:4px;background:${txt};opacity:0.25;border-radius:2px;width:70%;margin:0 auto"></div><div style="height:4px;background:${txt};opacity:0.25;border-radius:2px;width:60%;margin:0 auto"></div></div><div style="flex:1;background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:6px"><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:90%"></div></div></div>`);
3915
+ }
3916
+ if (/^(navbar|nav|navigation|breadcrumb|pagination|navigationmenu)$/.test(n) || /^Navigation$/.test(category)) {
3917
+ return wrap(`<div style="display:flex;align-items:center;gap:8px;background:${bg};border-bottom:1px solid ${brd};padding:6px 10px;width:100%;max-width:220px;box-sizing:border-box"><div style="height:6px;background:${acc};border-radius:2px;width:35px"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:28px"></div><div style="height:6px;background:${txt};opacity:0.25;border-radius:2px;width:32px"></div></div>`);
3918
+ }
3919
+ if (/^(progress)$/.test(n)) {
3920
+ return wrap(`<div style="width:80%;box-sizing:border-box"><div style="height:6px;background:${brd};border-radius:9999px;overflow:hidden"><div style="height:100%;width:65%;background:${acc};border-radius:9999px"></div></div></div>`);
3921
+ }
3922
+ if (/^(dropdownmenu|contextmenu|menubar|menu)$/.test(n)) {
3923
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;overflow:hidden;width:70%;box-sizing:border-box">${[["60%","#a0a"],["40%",""],["55%",""]].map(([w,a])=>`<div style="padding:4px 10px;display:flex;align-items:center;gap:6px;${a?`background:${acc}20`:``}"><div style="flex:0 0 ${w};height:5px;background:${txt};opacity:${a?0.5:0.25};border-radius:1px"></div></div>`).join("")}</div>`);
3924
+ }
3925
+ if (/^(popover|tooltip|hovercard)$/.test(n)) {
3926
+ return wrap(`<div style="background:${bg};border:1px solid ${brd};border-radius:${rad}px;padding:8px 12px;width:75%;box-sizing:border-box;box-shadow:0 4px 12px rgba(0,0,0,0.1)"><div style="height:6px;background:${txt};opacity:0.3;border-radius:2px;width:60%;margin-bottom:4px"></div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:80%"></div></div>`);
3927
+ }
3928
+ if (/^(resizable|splitpane)$/.test(n)) {
3929
+ return wrap(`<div style="display:flex;align-items:stretch;gap:0;width:85%;height:40px"><div style="flex:1;background:${bg};border:1px solid ${brd};border-radius:${rad}px 0 0 ${rad}px"></div><div style="width:5px;background:${brd};cursor:col-resize"></div><div style="flex:1;background:${bg};border:1px solid ${brd};border-left:none;border-radius:0 ${rad}px ${rad}px 0"></div></div>`);
3930
+ }
3931
+ if (/^(chart|pricechart|sparkline)$/.test(n)) {
3932
+ const pts = [22,18,28,15,24,12,20,16,26,10,22,18].map((y,x)=>`${x*14},${y}`).join(" ");
3933
+ return wrap(`<div style="width:85%;height:40px;position:relative"><svg viewBox="0 0 168 32" style="width:100%;height:100%"><polyline points="${pts}" fill="none" stroke="${acc}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></div>`);
3934
+ }
3935
+ if (/^(separator)$/.test(n)) {
3936
+ return wrap(`<div style="width:85%;height:1px;background:${brd}"></div>`);
3937
+ }
3938
+ // Generic page/section components → show token color stripe
3939
+ if (colorSwatches.length > 0) {
3940
+ const stripeW = Math.floor(100 / colorSwatches.length);
3941
+ const stripes = colorSwatches.map(s => `<div style="flex:1;background:${s.hex};height:20px"></div>`).join("");
3942
+ return wrap(`<div style="width:85%;display:flex;flex-direction:column;gap:5px"><div style="display:flex;border-radius:4px;overflow:hidden;height:12px">${stripes}</div><div style="height:5px;background:${txt};opacity:0.2;border-radius:2px;width:60%"></div><div style="height:5px;background:${txt};opacity:0.15;border-radius:2px;width:40%"></div></div>`);
3943
+ }
3944
+ // Absolute fallback: neutral box
3945
+ return wrap(`<div style="width:85%;height:28px;background:${bg};border:1px solid ${brd};border-radius:${rad}px"></div>`);
3946
+ }
3947
+
3770
3948
  /**
3771
3949
  * Generates Foundations/Component Inventory story.
3772
3950
  * Shows all project components classified by Atlassian-style functional category.
@@ -3782,23 +3960,47 @@ function writeComponentInventoryStory(components, foundations) {
3782
3960
  "Forms and Input", "Status Indicators", "Navigation", "Overlays and Layering",
3783
3961
  "Loading", "Messaging", "Images and Icons", "Layout and Structure", "Text and Data Display",
3784
3962
  ];
3785
- const categorized = {}; // category → [{ name, group, tokenCount, propCount }]
3963
+ const categorized = {}; // category → [{ name, group, tokenCount, propCount, colorSwatches }]
3964
+ const foundColors = (foundations && foundations.colors) ? foundations.colors : {};
3786
3965
 
3787
3966
  for (const comp of components) {
3788
3967
  const g = comp.group || "Components";
3789
3968
  const gLower = g.toLowerCase();
3790
- const isShadcnGroup = gLower === "shadcn" || gLower === "ui";
3791
3969
  const tokens = Array.isArray(comp.tokens) ? comp.tokens : [];
3792
3970
  const props = Array.isArray(comp.props) ? comp.props : [];
3793
3971
  const semantic = classifyComponent(comp.name, tokens, props);
3794
3972
  const category = semantic || g;
3795
3973
 
3974
+ // Resolve color tokens to hex swatches from foundations.colors (max 6)
3975
+ const colorSwatches = tokens
3976
+ .filter(t => !/:/.test(t) &&
3977
+ /^(bg|text|border|ring|from|to|fill|stroke)-/.test(t) &&
3978
+ // Exclude border-side shorthands (b, t, l, r, x, y) and alignment/size text-* tokens
3979
+ !/^border-[btlrxy]$/.test(t) &&
3980
+ !/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl|\d|left|right|center|justify|start|end|wrap|nowrap|balance|pretty|clip|ellipsis)/.test(t))
3981
+ .slice(0, 6)
3982
+ .map(token => {
3983
+ const m = token.match(/^(?:bg|text|border|ring|from|to|fill|stroke)-(.+)$/);
3984
+ let key = m ? m[1] : null;
3985
+ // Strip opacity modifier: "muted/20" → "muted", "primary/5" → "primary"
3986
+ if (key) key = key.replace(/\/\d+$/, "");
3987
+ const entry = key ? foundColors[key] : null;
3988
+ const hex = entry?.hex && /^#[0-9a-fA-F]{3,8}$/.test(entry.hex) ? entry.hex : null;
3989
+ return { token, hex };
3990
+ })
3991
+ .filter(s => s.hex); // only swatches with resolved hex
3992
+
3993
+ // Visual preview HTML (computed at generation time using project's resolved colors)
3994
+ const previewHtml = buildInventoryPreviewHtml(comp.name, category, colorSwatches, tokens.filter(t => !/:/.test(t)));
3995
+
3796
3996
  if (!categorized[category]) categorized[category] = [];
3797
3997
  categorized[category].push({
3798
3998
  name: comp.name,
3799
3999
  group: g,
3800
4000
  tokenCount: tokens.filter(t => !/:/.test(t)).length,
3801
4001
  propCount: props.length,
4002
+ colorSwatches,
4003
+ previewHtml,
3802
4004
  });
3803
4005
  }
3804
4006
 
@@ -3828,24 +4030,24 @@ function writeComponentInventoryStory(components, foundations) {
3828
4030
  `export default meta;`,
3829
4031
  `type Story = StoryObj;`,
3830
4032
  ``,
3831
- `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number }[] }[] = ${JSON.stringify(inventoryData)};`,
4033
+ `const inventoryData: { category: string; components: { name: string; group: string; tokenCount: number; propCount: number; colorSwatches: { token: string; hex: string }[]; previewHtml: string }[] }[] = ${JSON.stringify(inventoryData)};`,
3832
4034
  `const totalComponents = ${totalComponents};`,
3833
4035
  `const uniqueTokens = ${uniqueTokens};`,
3834
4036
  ``,
3835
4037
  `export const Default: Story = {`,
3836
4038
  ` render: () => (`,
3837
- ` <div style={{ padding: 32, background: "#fff", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh" }}>`,
4039
+ ` <div style={{ padding: 32, background: "#f8f9fa", fontFamily: "system-ui,sans-serif", color: "#111", minHeight: "100vh", width: "100%" }}>`,
3838
4040
  ` {/* Header */}`,
3839
4041
  ` <div style={{ marginBottom: 40 }}>`,
3840
4042
  ` <h2 style={{ fontSize: 28, fontWeight: 700, margin: "0 0 8px" }}>Component Inventory</h2>`,
3841
- ` <p style={{ fontSize: 14, color: "#6b7280", margin: "0 0 20px" }}>All components detected in this project, classified by function.</p>`,
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>`,
3842
4044
  ` <div style={{ display: "flex", gap: 12, flexWrap: "wrap" as any }}>`,
3843
4045
  ` {[`,
3844
4046
  ` { value: totalComponents.toString(), label: "Components" },`,
3845
4047
  ` { value: inventoryData.length.toString(), label: "Categories" },`,
3846
4048
  ` { value: uniqueTokens.toString(), label: "Unique Tokens" },`,
3847
4049
  ` ].map(stat => (`,
3848
- ` <div key={stat.label} style={{ padding: "12px 20px", background: "#f3f4f6", borderRadius: 10, textAlign: "center" as any, minWidth: 90 }}>`,
4050
+ ` <div key={stat.label} style={{ padding: "12px 20px", background: "#fff", borderRadius: 10, textAlign: "center" as any, minWidth: 90, border: "1px solid #e5e7eb" }}>`,
3849
4051
  ` <div style={{ fontSize: 26, fontWeight: 800, color: "#111", lineHeight: 1 }}>{stat.value}</div>`,
3850
4052
  ` <div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>{stat.label}</div>`,
3851
4053
  ` </div>`,
@@ -3854,31 +4056,43 @@ function writeComponentInventoryStory(components, foundations) {
3854
4056
  ` </div>`,
3855
4057
  ` {/* Category groups */}`,
3856
4058
  ` {inventoryData.map(group => (`,
3857
- ` <div key={group.category} style={{ marginBottom: 40 }}>`,
3858
- ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #f3f4f6" }}>`,
4059
+ ` <div key={group.category} style={{ marginBottom: 44 }}>`,
4060
+ ` <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 16, paddingBottom: 10, borderBottom: "2px solid #e5e7eb" }}>`,
3859
4061
  ` <h3 style={{ margin: 0, fontSize: 15, fontWeight: 700, color: "#111" }}>{group.category}</h3>`,
3860
4062
  ` <span style={{ fontSize: 12, background: "#e0e7ff", color: "#3730a3", padding: "2px 9px", borderRadius: 12, fontWeight: 600 }}>{group.components.length}</span>`,
3861
4063
  ` </div>`,
3862
- ` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(190px, 1fr))", gap: 10 }}>`,
4064
+ ` <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))", gap: 12 }}>`,
3863
4065
  ` {group.components.map(comp => (`,
3864
- ` <div key={comp.name} style={{ border: "1px solid #e5e7eb", borderRadius: 10, padding: "14px 16px", background: "#fafafa" }}>`,
3865
- ` <div style={{ fontWeight: 700, fontSize: 14, color: "#111", marginBottom: 8 }}>{comp.name}</div>`,
3866
- ` <div style={{ display: "flex", gap: 5, flexWrap: "wrap" as any }}>`,
3867
- ` {comp.propCount > 0 && (`,
3868
- ` <span style={{ fontSize: 10, background: "#f3f4f6", color: "#6b7280", padding: "2px 7px", borderRadius: 10, border: "1px solid #e5e7eb" }}>`,
3869
- ` {comp.propCount} props`,
3870
- ` </span>`,
3871
- ` )}`,
3872
- ` {comp.tokenCount > 0 && (`,
3873
- ` <span style={{ fontSize: 10, background: "#eff6ff", color: "#1d4ed8", padding: "2px 7px", borderRadius: 10, border: "1px solid #dbeafe" }}>`,
3874
- ` {comp.tokenCount} tokens`,
3875
- ` </span>`,
3876
- ` )}`,
3877
- ` {comp.group && comp.group !== "shadcn" && comp.group !== "UI" && comp.group !== "Components" && (`,
3878
- ` <span style={{ fontSize: 10, background: "#f0fdf4", color: "#166534", padding: "2px 7px", borderRadius: 10, border: "1px solid #bbf7d0" }}>`,
3879
- ` {comp.group}`,
3880
- ` </span>`,
3881
- ` )}`,
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>`,
3882
4096
  ` </div>`,
3883
4097
  ` </div>`,
3884
4098
  ` ))}`,
@@ -3894,6 +4108,46 @@ function writeComponentInventoryStory(components, foundations) {
3894
4108
  console.log("[VDS] Wrote " + path.relative(PROJECT_ROOT, path.join(foundationsDir, "ComponentInventory.stories.tsx")));
3895
4109
  }
3896
4110
 
4111
+ /**
4112
+ * Patches .storybook/preview.tsx so that Foundations stories bypass app-specific
4113
+ * provider decorators (SidebarProvider, etc.) that constrain child width in flex layout.
4114
+ * Uses the context.title to detect "Foundations/" prefix at runtime.
4115
+ */
4116
+ function patchPreviewTsxFoundations() {
4117
+ const candidates = [
4118
+ path.join(PROJECT_ROOT, ".storybook", "preview.tsx"),
4119
+ path.join(PROJECT_ROOT, ".storybook", "preview.ts"),
4120
+ path.join(PROJECT_ROOT, ".storybook", "preview.jsx"),
4121
+ path.join(PROJECT_ROOT, ".storybook", "preview.js"),
4122
+ ];
4123
+ const previewPath = candidates.find(p => fs.existsSync(p));
4124
+ if (!previewPath) return;
4125
+
4126
+ let content;
4127
+ try { content = fs.readFileSync(previewPath, "utf-8"); } catch { return; }
4128
+
4129
+ // Already patched?
4130
+ if (content.includes('startsWith("Foundations")')) return;
4131
+
4132
+ // Match: const withProviders = (Story: any) => React.createElement(AnyProvider, null, React.createElement(Story));
4133
+ const re = /const withProviders = \(Story(?:: any)?\) => (React\.createElement\([^;]+\));/;
4134
+ const match = content.match(re);
4135
+ if (!match) return; // Pattern not found — nothing to patch
4136
+
4137
+ const providerExpr = match[1]; // e.g. React.createElement(SidebarProvider, null, React.createElement(Story))
4138
+ const patched =
4139
+ `const withProviders = (Story: any, context: any) => ` +
4140
+ `context?.title?.startsWith("Foundations") ? React.createElement(Story) : ${providerExpr};`;
4141
+
4142
+ content = content.replace(re, patched);
4143
+ try {
4144
+ fs.writeFileSync(previewPath, content, "utf-8");
4145
+ console.log("[VDS] Patched .storybook/preview.tsx — Foundations stories now bypass app providers");
4146
+ } catch (e) {
4147
+ console.warn("[VDS] Could not patch preview.tsx:", e.message);
4148
+ }
4149
+ }
4150
+
3897
4151
  function writeChangelogStory(changelog) {
3898
4152
  const foundationsDir = path.join(STORIES_DIR, "foundations");
3899
4153
  ensureDir(foundationsDir);
@@ -3910,7 +4164,7 @@ function writeChangelogStory(changelog) {
3910
4164
  "",
3911
4165
  "export const Default: Story = {",
3912
4166
  " render: () => (",
3913
- " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", color: \"#111\" }}>",
4167
+ " <div style={{ fontFamily: \"system-ui,sans-serif\", padding: 32, background: \"#fff\", minHeight: \"100vh\", width: \"100%\", color: \"#111\" }}>",
3914
4168
  " <h2>CHANGELOG</h2>",
3915
4169
  " {changelog.length === 0 ? <p>No changes recorded yet.</p> : changelog.map((entry) => (",
3916
4170
  " <div key={entry.version}>",
@@ -4168,6 +4422,7 @@ function main() {
4168
4422
  }
4169
4423
  writeComponentInventoryStory(components, foundations);
4170
4424
  writeChangelogStory(data.changelog);
4425
+ patchPreviewTsxFoundations();
4171
4426
  try {
4172
4427
  const fd = path.join(STORIES_DIR, "foundations");
4173
4428
  if (fs.existsSync(fd)) {