vibe-design-system 2.8.3 → 2.8.5

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.3",
3
+ "version": "2.8.5",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -294,6 +294,29 @@ function buildWithProvidersCode(providers) {
294
294
  return `const withProviders = (Story: any) => ${inner};`;
295
295
  }
296
296
 
297
+ /** ErrorBoundary class – injected into .storybook/preview to catch story render errors (missing providers, undefined props, etc.) */
298
+ function buildErrorBoundaryCode() {
299
+ return [
300
+ "class VdsErrorBoundary extends React.Component {",
301
+ " constructor(props) { super(props); this.state = { error: null }; }",
302
+ " static getDerivedStateFromError(error) { return { error }; }",
303
+ " componentDidCatch(err) { console.error(\"[VDS Story Error]\", err); }",
304
+ " render() {",
305
+ " const s = this.state; const p = this.props;",
306
+ " if (s && s.error) return React.createElement(\"div\", {",
307
+ " style: { padding: 16, color: \"#ff6b6b\", border: \"1px solid #ff6b6b\", borderRadius: 8, fontFamily: \"monospace\", margin: 8 }",
308
+ " }, React.createElement(\"b\", null, \"⚠️ Story Error: \"), React.createElement(\"code\", { style: { whiteSpace: \"pre-wrap\" } }, String(s.error.message)));",
309
+ " return p.children;",
310
+ " }",
311
+ "}",
312
+ "const withErrorBoundary = (Story) => React.createElement(VdsErrorBoundary, null, React.createElement(Story));",
313
+ ].join("\n");
314
+ }
315
+
316
+ // VDS markers for idempotent preview injection
317
+ const VDS_BLOCK_START = "// [VDS:providers-start]";
318
+ const VDS_BLOCK_END = "// [VDS:providers-end]";
319
+
297
320
  function injectProviderDecorators(projectRoot) {
298
321
  const previewPath = getPreviewPath(projectRoot);
299
322
  if (!previewPath) {
@@ -302,44 +325,83 @@ function injectProviderDecorators(projectRoot) {
302
325
  }
303
326
 
304
327
  const { providersToAdd, hooksWithoutProvider, hooksByFile } = collectProvidersAndWarnings(projectRoot);
305
- if (providersToAdd.length === 0 && hooksWithoutProvider.size === 0) return;
306
-
307
- if (providersToAdd.length > 0) {
308
- let content = fs.readFileSync(previewPath, "utf-8");
309
- const uniqueProviders = [...new Map(providersToAdd.map((p) => [p.name, p])).values()];
310
- if (!content.includes("import React") && !content.includes("import React from")) {
311
- const firstImport = content.match(/^import\s+/m);
312
- const insertAt = firstImport ? content.indexOf(firstImport[0]) : 0;
313
- content = content.slice(0, insertAt) + "import React from \"react\";\n" + content.slice(insertAt);
314
- }
315
- for (const p of uniqueProviders) {
316
- if (!content.includes(p.name)) {
317
- const lastImportIdx = content.search(/\nimport\s+.+?;\s*$/m);
318
- const insertAt = lastImportIdx >= 0 ? content.indexOf("\n", lastImportIdx) + 1 : content.indexOf("\n") + 1;
319
- let importLine = `import { ${p.name} } from "${p.importPath}";\n`;
320
- if (p.extraImport && !content.includes(p.extraImport.name)) {
321
- importLine += `import { ${p.extraImport.name} } from "${p.extraImport.from}";\n`;
322
- }
323
- content = content.slice(0, insertAt) + importLine + content.slice(insertAt);
328
+ let content = fs.readFileSync(previewPath, "utf-8");
329
+
330
+ // 1. Ensure React import exists (needed for error boundary + providers)
331
+ if (!content.includes("import React") && !content.includes("import React from")) {
332
+ const firstImport = content.match(/^import\s+/m);
333
+ const insertAt = firstImport ? content.indexOf(firstImport[0]) : 0;
334
+ content = content.slice(0, insertAt) + "import React from \"react\";\n" + content.slice(insertAt);
335
+ }
336
+
337
+ // 2. Add provider imports if not already present (use regex to detect existing import)
338
+ const uniqueProviders = [...new Map(providersToAdd.map((p) => [p.name, p])).values()];
339
+ for (const p of uniqueProviders) {
340
+ const alreadyImported = new RegExp(`\\bimport[^;]*\\b${p.name}\\b[^;]*from`).test(content);
341
+ if (!alreadyImported) {
342
+ const importMatches = [...content.matchAll(/^import\s+[^\n]+;/gm)];
343
+ const lastMatch = importMatches[importMatches.length - 1];
344
+ const insertAt = lastMatch ? lastMatch.index + lastMatch[0].length + 1 : content.indexOf("\n") + 1;
345
+ let importLine = `import { ${p.name} } from "${p.importPath}";\n`;
346
+ if (p.extraImport && !new RegExp(`\\b${p.extraImport.name}\\b`).test(content)) {
347
+ importLine += `import { ${p.extraImport.name} } from "${p.extraImport.from}";\n`;
324
348
  }
349
+ content = content.slice(0, insertAt) + importLine + content.slice(insertAt);
325
350
  }
326
- content = content.replace(/const withProviders\s*=\s*\([^)]*\)\s*=>\s*[\s\S]+?;\s*\n?/g, "").replace(/\n{3,}/g, "\n\n");
327
- const withProvidersCode = buildWithProvidersCode(providersToAdd);
328
- const insertBefore = content.indexOf("const preview");
329
- if (insertBefore !== -1) {
330
- content = content.slice(0, insertBefore) + withProvidersCode + "\n\n" + content.slice(insertBefore);
331
- if (content.includes("decorators:")) {
332
- if (!content.includes("decorators: [withProviders")) {
333
- content = content.replace(/decorators:\s*\[/, "decorators: [withProviders, ");
334
- }
335
- } else {
336
- content = content.replace(/(const preview\s*[^=]*=\s*\{\s*)/, "$1\n decorators: [withProviders],\n ");
351
+ }
352
+
353
+ // 3. Remove previous VDS injections (idempotent re-runs)
354
+ content = content.replace(new RegExp(VDS_BLOCK_START.replace(/[\[\]]/g, "\\$&") + "[\\s\\S]*?" + VDS_BLOCK_END.replace(/[\[\]]/g, "\\$&") + "\\n?"), "");
355
+ // Remove old-style single-line withProviders (pre-marker era)
356
+ content = content.replace(/^const withProviders\s*=\s*\([^)]*\)\s*=>.*;\n?/m, "");
357
+ // Remove VDS decorator names from decorators arrays so we can re-inject cleanly
358
+ content = content.replace(/\bwithErrorBoundary\s*,\s*/g, "").replace(/,\s*\bwithErrorBoundary\b/g, "").replace(/\bwithErrorBoundary\b/g, "");
359
+ content = content.replace(/\bwithProviders\s*,\s*/g, "").replace(/,\s*\bwithProviders\b/g, "").replace(/\bwithProviders\b/g, "");
360
+ // Fix any syntax damage: leading comma, trailing comma, empty decorators
361
+ content = content.replace(/\[\s*,\s*/g, "[").replace(/,\s*\]/g, "]");
362
+ content = content.replace(/\s*decorators:\s*\[\s*\]\s*,?\n?/g, "");
363
+ content = content.replace(/\n{3,}/g, "\n\n");
364
+
365
+ // 4. Build new VDS block (error boundary + optional provider wrapper)
366
+ const errorBoundaryCode = buildErrorBoundaryCode();
367
+ const withProvidersCode = uniqueProviders.length > 0 ? buildWithProvidersCode(providersToAdd) : null;
368
+ const vdsCode = [errorBoundaryCode, withProvidersCode].filter(Boolean).join("\n\n");
369
+ const vdsBlock = VDS_BLOCK_START + "\n" + vdsCode + "\n" + VDS_BLOCK_END;
370
+
371
+ // 5. Find insertion point: before "const preview" or "export default {"
372
+ let insertBefore = content.indexOf("const preview");
373
+ if (insertBefore === -1) {
374
+ const edMatch = content.match(/export\s+default\s*[\{(]/);
375
+ if (edMatch) insertBefore = content.indexOf(edMatch[0]);
376
+ }
377
+ if (insertBefore === -1) insertBefore = content.length;
378
+ content = content.slice(0, insertBefore) + vdsBlock + "\n\n" + content.slice(insertBefore);
379
+
380
+ // 6. Inject decorators into preview object
381
+ const decoratorItems = ["withErrorBoundary", ...(withProvidersCode ? ["withProviders"] : [])];
382
+ const decoratorsStr = decoratorItems.join(", ");
383
+ if (content.includes("decorators:")) {
384
+ // Prepend VDS decorators to existing decorators array
385
+ content = content.replace(/\bdecorators:\s*\[/, `decorators: [${decoratorsStr}, `);
386
+ content = content.replace(/,\s*\]/g, "]");
387
+ } else {
388
+ // Add new decorators key to preview object
389
+ const constPreviewMatch = content.match(/(const\s+preview\s*[^=\n]*=\s*\{)/);
390
+ if (constPreviewMatch) {
391
+ content = content.replace(constPreviewMatch[0], constPreviewMatch[0] + `\n decorators: [${decoratorsStr}],`);
392
+ } else {
393
+ const exportDefaultMatch = content.match(/(export\s+default\s*\{)/);
394
+ if (exportDefaultMatch) {
395
+ content = content.replace(exportDefaultMatch[0], exportDefaultMatch[0] + `\n decorators: [${decoratorsStr}],`);
337
396
  }
338
397
  }
339
- fs.writeFileSync(previewPath, content, "utf-8");
340
- console.log("[VDS] Storybook preview: " + uniqueProviders.map((p) => p.name).join(", "));
341
398
  }
342
399
 
400
+ content = content.replace(/\n{3,}/g, "\n\n");
401
+ fs.writeFileSync(previewPath, content, "utf-8");
402
+ console.log("[VDS] Storybook preview: ErrorBoundary" + (uniqueProviders.length ? " + " + uniqueProviders.map((p) => p.name).join(", ") : ""));
403
+
404
+ // 7. Handle hooks without provider: add warning comment to affected story files
343
405
  if (hooksWithoutProvider.size > 0) {
344
406
  const componentNameFromPath = (rel) => path.basename(rel, path.extname(rel));
345
407
  const storiesDir = path.join(projectRoot, "src", "stories");
@@ -523,7 +523,7 @@ const COMPONENT_EXTRA_ARGS = {
523
523
  " taskId: \"1\",",
524
524
  ],
525
525
  TimeFilterPanel: [
526
- " filter: { dateRange: {}, userIds: [], tags: [], billable: \"all\" },",
526
+ " filter: { preset: null, dateRange: {}, userIds: [], tags: [], billable: \"all\" },",
527
527
  " onFilterChange: () => {},",
528
528
  " onSaveFilter: () => {},",
529
529
  " onDeleteFilter: () => {},",
@@ -538,6 +538,12 @@ const COMPONENT_EXTRA_ARGS = {
538
538
  " totalEstimated: 0,",
539
539
  " projectTotalEstimated: 0,",
540
540
  ],
541
+ TimeTable: [
542
+ " logs: [{ id: \"1\", taskId: \"t1\", userId: \"u1\", date: \"2024-01-15\", hours: 8, description: \"Work session\", billable: true, status: \"submitted\" }, { id: \"2\", taskId: \"t1\", userId: \"u1\", date: \"2024-01-16\", hours: 6, description: \"Review session\", billable: false, status: \"draft\" }],",
543
+ " users: [],",
544
+ " tasks: [],",
545
+ " onLogClick: () => {},",
546
+ ],
541
547
  };
542
548
 
543
549
  /** Render'da args'a uygulanacak fallback (useState(estimate.toString()) gibi kullanımlar için; args/Controls undefined yapsa bile). */
@@ -548,8 +554,9 @@ const RENDER_ARGS_FALLBACKS = {
548
554
  /** Kalıcı çözüm: Storybook Docs/Controls args'ı geçmezse bile component'a güvenli props veren wrapper. Önce safeDefaults uygulanır, sonra gelen props. */
549
555
  const SAFE_WRAPPER_DEFAULTS = {
550
556
  TaskEstimateInput: `{ estimate: 0, onUpdate: () => {}, value: 0, task: { id: "1", title: "Example", estimate: 0 }, compact: false }`,
551
- TimeFilterPanel: `{ filter: { dateRange: {}, userIds: [], tags: [], billable: "all" }, onFilterChange: () => {}, onSaveFilter: () => {}, onDeleteFilter: () => {}, filteredCount: 0, savedFilters: [] }`,
557
+ TimeFilterPanel: `{ filter: { preset: null, dateRange: {}, userIds: [], tags: [], billable: "all" }, onFilterChange: () => {}, onSaveFilter: () => {}, onDeleteFilter: () => {}, filteredCount: 0, savedFilters: [] }`,
552
558
  TimeStats: `{ totalLogged: 0, totalBillable: 0, totalNonBillable: 0, totalBilled: 0, totalEstimated: 0, projectTotalEstimated: 0 }`,
559
+ TimeTable: `{ logs: [{ id: "1", taskId: "t1", userId: "u1", date: "2024-01-15", hours: 8, description: "Work session", billable: true, status: "submitted" }, { id: "2", taskId: "t1", userId: "u1", date: "2024-01-16", hours: 6, description: "Review session", billable: false, status: "draft" }], users: [], tasks: [], onLogClick: () => {} }`,
553
560
  };
554
561
 
555
562
  /** Recursive list of .tsx/.jsx file paths under dir (relative to dir). Index.tsx / index.tsx first for deterministic "first usage". */
@@ -821,15 +828,23 @@ function buildDefaultArgsForRequiredProps(props, usageFromPages = null, componen
821
828
  }
822
829
  }
823
830
  }
824
- // COMPONENT_EXTRA_ARGS: props listesi boş olsa bile (örn. parse edilemeyen component) uygula
831
+ // COMPONENT_EXTRA_ARGS: props listesi boş olsa bile (örn. parse edilemeyen component) uygula.
832
+ // Bu blok her zaman override eder — auto-generated değerleri daha doğru olanlarla değiştirir.
825
833
  if (componentName && COMPONENT_EXTRA_ARGS[componentName]) {
826
834
  for (const line of COMPONENT_EXTRA_ARGS[componentName]) {
827
835
  const keyMatch = line.match(/^\s*(\w+)\s*:/);
828
836
  const key = keyMatch ? keyMatch[1] : null;
829
- if (key && !added.has(key)) {
830
- argLines.push(line);
831
- added.add(key);
837
+ if (!key) continue;
838
+ if (added.has(key)) {
839
+ // Remove the previously auto-generated line for this key so we can replace it
840
+ const idx = argLines.findIndex(l => {
841
+ const m = l.match(/^\s*(\w+)\s*:/);
842
+ return m && m[1] === key;
843
+ });
844
+ if (idx !== -1) argLines.splice(idx, 1);
832
845
  }
846
+ argLines.push(line);
847
+ added.add(key);
833
848
  }
834
849
  }
835
850
  for (const name of Object.keys(fromPages)) {