vibe-design-system 2.8.27 → 2.8.28

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.27",
3
+ "version": "2.8.28",
4
4
  "description": "Auto-generate design systems for vibe coding projects",
5
5
  "homepage": "https://vibedesign.tech",
6
6
  "repository": {
@@ -525,6 +525,61 @@ function extractTSProps(content) {
525
525
  return props.length > 0 ? props : null;
526
526
  }
527
527
 
528
+ /**
529
+ * Extract named UI patterns from a feature-tier file.
530
+ *
531
+ * Signal 1 — JSX block comments: {/* SECTION TITLE *\/}
532
+ * e.g. {/* CLUSTER 1: REVENUE & BILLING *\/} → label "CLUSTER 1: REVENUE & BILLING"
533
+ *
534
+ * Signal 2 — Nearest <h3>/<h2> text (fallback when no comments found)
535
+ * e.g. <h3>Unbilled WIP</h3> → label "Unbilled WIP"
536
+ *
537
+ * Returns Array<{ label: string, line: number }> or null.
538
+ * Safe on any JSX/TSX file — never throws.
539
+ */
540
+ function extractInlinePatterns(content) {
541
+ const patterns = [];
542
+ const seen = new Set();
543
+
544
+ // Signal 1: JSX block comments — {/* ... */}
545
+ const commentRe = /\{\/\*\s*([\s\S]{3,120}?)\s*\*\/\}/g;
546
+ let cm;
547
+ while ((cm = commentRe.exec(content)) !== null) {
548
+ const raw = cm[1].replace(/\s+/g, " ").trim();
549
+ // Skip too-short, pure annotation, or code-style comments
550
+ if (!raw || raw.length < 5) continue;
551
+ if (/^(TODO|FIXME|HACK|NOTE|@|eslint|prettier|ts-ignore|type-)/i.test(raw)) continue;
552
+ // Skip comments that look like code (contain JSX/HTML tags)
553
+ if (/<[A-Za-z]/.test(raw)) continue;
554
+ const lineNum = content.slice(0, cm.index).split("\n").length;
555
+ const label = raw.slice(0, 80);
556
+ const key = label.toLowerCase().replace(/\s+/g, " ");
557
+ if (!seen.has(key)) {
558
+ seen.add(key);
559
+ patterns.push({ label, line: lineNum });
560
+ }
561
+ }
562
+
563
+ // Signal 2: <h3>/<h2> text content (only if no JSX comments found)
564
+ if (patterns.length === 0) {
565
+ const headingRe = /<h[23][^>]*>\s*([^<{]{4,60}?)\s*<\/h[23]>/g;
566
+ let hm;
567
+ while ((hm = headingRe.exec(content)) !== null) {
568
+ const raw = hm[1].replace(/\s+/g, " ").trim();
569
+ if (!raw || raw.length < 4) continue;
570
+ const lineNum = content.slice(0, hm.index).split("\n").length;
571
+ const label = raw.slice(0, 80);
572
+ const key = label.toLowerCase();
573
+ if (!seen.has(key)) {
574
+ seen.add(key);
575
+ patterns.push({ label, line: lineNum });
576
+ }
577
+ }
578
+ }
579
+
580
+ return patterns.length > 0 ? patterns : null;
581
+ }
582
+
528
583
  function getAllComponentFiles(dir, baseDir = dir) {
529
584
  const entries = fs.readdirSync(dir, { withFileTypes: true });
530
585
  const files = [];
@@ -2304,9 +2359,10 @@ function scan() {
2304
2359
  const tier = inferTier(rel, content);
2305
2360
  const variants = (tier === "primitive") ? extractCvaVariants(content) : null;
2306
2361
  const props = (tier === "component" || tier === "feature") ? extractTSProps(content) : null;
2362
+ const patterns = (tier === "feature") ? extractInlinePatterns(content) : null;
2307
2363
  const lineCount = content.split("\n").length;
2308
2364
  const localImportCount = (content.match(/from\s+['"]\.\.\?\//g) || []).length;
2309
- results.push({ file: rel, name, group, category, description, tokens, tier, lines: lineCount, localImports: localImportCount, ...(variants ? { variants } : {}), ...(props ? { props } : {}) });
2365
+ results.push({ file: rel, name, group, category, description, tokens, tier, lines: lineCount, localImports: localImportCount, ...(variants ? { variants } : {}), ...(props ? { props } : {}), ...(patterns ? { patterns } : {}) });
2310
2366
  }
2311
2367
  if (PAGES_DIR && fs.existsSync(PAGES_DIR)) {
2312
2368
  const pageFiles = getAllComponentFiles(PAGES_DIR);
@@ -2669,13 +2669,17 @@ function writeCursorRules(components, foundations) {
2669
2669
 
2670
2670
  // Tier 3 — Features
2671
2671
  if (byTier.feature.length > 0) {
2672
- lines.push(`## ⚙️ Feature ComponentsExtract patterns, don't import whole`);
2673
- lines.push(`> Complex components with internal state. Reuse sub-patterns, not the whole file.`);
2672
+ lines.push(`## ⚙️ Feature ViewsReuse existing UI patterns before building new ones`);
2673
+ lines.push(`> Full views with internal state. When a user story needs a new widget, check the pattern list below first.`);
2674
2674
  lines.push(``);
2675
2675
  for (const c of byTier.feature.sort((a, b) => a.name.localeCompare(b.name))) {
2676
2676
  lines.push(`- **${c.name}** \`${c.file}\``);
2677
2677
  const propsLine = formatProps(c.props);
2678
2678
  if (propsLine) lines.push(propsLine);
2679
+ if (Array.isArray(c.patterns) && c.patterns.length > 0) {
2680
+ const patternParts = c.patterns.slice(0, 8).map(p => `"${p.label}" (line ${p.line})`);
2681
+ lines.push(` patterns: ${patternParts.join(" · ")}`);
2682
+ }
2679
2683
  }
2680
2684
  lines.push(``);
2681
2685
  lines.push(`---`);