@getcoherent/cli 0.5.8 → 0.5.9

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.
Files changed (2) hide show
  1. package/dist/index.js +154 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3613,6 +3613,16 @@ ANTI-PATTERNS (NEVER DO):
3613
3613
  - Mixing link styles on the same page \u2192 pick ONE style
3614
3614
  - Interactive elements without hover/focus states
3615
3615
 
3616
+ COMPONENT VARIANT RULES (CRITICAL):
3617
+ - NEVER use <Button> with custom bg-*/text-* classes for navigation or tabs without variant="ghost".
3618
+ The default Button variant sets bg-primary, so custom text-muted-foreground or bg-accent classes will conflict.
3619
+ BAD: <Button className="text-muted-foreground hover:bg-accent">Tab</Button>
3620
+ GOOD: <Button variant="ghost" className="text-muted-foreground">Tab</Button>
3621
+ BEST: Use shadcn <Tabs> / <TabsList> / <TabsTrigger> for tab-style navigation.
3622
+ - For sidebar navigation buttons, ALWAYS use variant="ghost" with active-state classes:
3623
+ <Button variant="ghost" className={cn("w-full justify-start", isActive && "bg-accent font-medium")}>
3624
+ - For filter toggle buttons, use variant={isActive ? 'default' : 'outline'} \u2014 NOT className toggling.
3625
+
3616
3626
  CONTENT (zero placeholders):
3617
3627
  - NEVER: "Lorem ipsum", "Card content", "Description here"
3618
3628
  - ALWAYS: Real, contextual content. Realistic metric names, values, dates.
@@ -5060,6 +5070,121 @@ function fixGlobalsCss(projectRoot, config2) {
5060
5070
  writeFileSync7(layoutPath, layoutContent, "utf-8");
5061
5071
  }
5062
5072
 
5073
+ // src/utils/component-rules.ts
5074
+ function extractJsxElementProps(code, openTagStart) {
5075
+ let i = openTagStart;
5076
+ if (code[i] !== "<") return null;
5077
+ i++;
5078
+ let braceDepth = 0;
5079
+ let inSingleQuote = false;
5080
+ let inDoubleQuote = false;
5081
+ let inTemplateLiteral = false;
5082
+ let escaped = false;
5083
+ while (i < code.length) {
5084
+ const ch = code[i];
5085
+ if (escaped) {
5086
+ escaped = false;
5087
+ i++;
5088
+ continue;
5089
+ }
5090
+ if (ch === "\\" && (inSingleQuote || inDoubleQuote || inTemplateLiteral)) {
5091
+ escaped = true;
5092
+ i++;
5093
+ continue;
5094
+ }
5095
+ if (!inDoubleQuote && !inTemplateLiteral && ch === "'" && braceDepth > 0) {
5096
+ inSingleQuote = !inSingleQuote;
5097
+ } else if (!inSingleQuote && !inTemplateLiteral && ch === '"') {
5098
+ inDoubleQuote = !inDoubleQuote;
5099
+ } else if (!inSingleQuote && !inDoubleQuote && ch === "`") {
5100
+ inTemplateLiteral = !inTemplateLiteral;
5101
+ }
5102
+ if (!inSingleQuote && !inDoubleQuote && !inTemplateLiteral) {
5103
+ if (ch === "{") braceDepth++;
5104
+ else if (ch === "}") braceDepth--;
5105
+ else if (ch === ">" && braceDepth === 0) {
5106
+ return code.slice(openTagStart, i + 1);
5107
+ }
5108
+ }
5109
+ i++;
5110
+ }
5111
+ return null;
5112
+ }
5113
+ var NAV_STYLE_SIGNAL = /text-muted-foreground/;
5114
+ var buttonMissingGhostVariant = {
5115
+ id: "button-missing-ghost-variant",
5116
+ component: "Button",
5117
+ detect(code) {
5118
+ const issues = [];
5119
+ const buttonRe = /<Button\s/g;
5120
+ let match;
5121
+ while ((match = buttonRe.exec(code)) !== null) {
5122
+ const props = extractJsxElementProps(code, match.index);
5123
+ if (!props) continue;
5124
+ if (/\bvariant\s*=/.test(props)) continue;
5125
+ if (!NAV_STYLE_SIGNAL.test(props)) continue;
5126
+ const line = code.slice(0, match.index).split("\n").length;
5127
+ issues.push({
5128
+ line,
5129
+ type: "BUTTON_MISSING_VARIANT",
5130
+ message: '<Button> with navigation-style classes (text-muted-foreground) but no variant \u2014 add variant="ghost"',
5131
+ severity: "error"
5132
+ });
5133
+ }
5134
+ return issues;
5135
+ },
5136
+ fix(code) {
5137
+ let result = code;
5138
+ let applied = false;
5139
+ const buttonRe = /<Button\s/g;
5140
+ let match;
5141
+ let offset = 0;
5142
+ while ((match = buttonRe.exec(code)) !== null) {
5143
+ const adjustedIndex = match.index + offset;
5144
+ const props = extractJsxElementProps(result, adjustedIndex);
5145
+ if (!props) continue;
5146
+ if (/\bvariant\s*=/.test(props)) continue;
5147
+ if (!NAV_STYLE_SIGNAL.test(props)) continue;
5148
+ const insertPos = adjustedIndex + "<Button".length;
5149
+ const insertion = "\n" + getIndent(result, adjustedIndex) + ' variant="ghost"';
5150
+ result = result.slice(0, insertPos) + insertion + result.slice(insertPos);
5151
+ offset += insertion.length;
5152
+ applied = true;
5153
+ }
5154
+ return {
5155
+ code: result,
5156
+ applied,
5157
+ description: 'added variant="ghost" to Button with nav-style classes'
5158
+ };
5159
+ }
5160
+ };
5161
+ function getIndent(code, pos) {
5162
+ const lineStart = code.lastIndexOf("\n", pos);
5163
+ const lineContent = code.slice(lineStart + 1, pos);
5164
+ const indentMatch = lineContent.match(/^(\s*)/);
5165
+ return indentMatch ? indentMatch[1] : "";
5166
+ }
5167
+ var rules = [buttonMissingGhostVariant];
5168
+ function detectComponentIssues(code) {
5169
+ const issues = [];
5170
+ for (const rule of rules) {
5171
+ issues.push(...rule.detect(code));
5172
+ }
5173
+ return issues;
5174
+ }
5175
+ function applyComponentRules(code) {
5176
+ const fixes = [];
5177
+ let result = code;
5178
+ for (const rule of rules) {
5179
+ const { code: fixed, applied, description } = rule.fix(result);
5180
+ if (applied) {
5181
+ result = fixed;
5182
+ fixes.push(description);
5183
+ }
5184
+ }
5185
+ return { code: result, fixes };
5186
+ }
5187
+
5063
5188
  // src/utils/quality-validator.ts
5064
5189
  var RAW_COLOR_RE = /(?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?(?:bg|text|border|ring|outline|from|to|via)-(gray|blue|red|green|yellow|purple|pink|indigo|orange|slate|zinc|stone|neutral|emerald|teal|cyan|sky|violet|fuchsia|rose|amber|lime)-\d+/g;
5065
5190
  var HEX_IN_CLASS_RE = /className="[^"]*#[0-9a-fA-F]{3,8}[^"]*"/g;
@@ -5482,6 +5607,7 @@ function validatePageQuality(code, validRoutes) {
5482
5607
  severity: "error"
5483
5608
  });
5484
5609
  }
5610
+ issues.push(...detectComponentIssues(code));
5485
5611
  return issues;
5486
5612
  }
5487
5613
  function replaceRawColors(classes, colorMap) {
@@ -5919,6 +6045,11 @@ ${selectImport}`
5919
6045
  if (fixed !== beforeLinkFix) {
5920
6046
  fixes.push("Link>Button \u2192 Button asChild>Link (DOM nesting fix)");
5921
6047
  }
6048
+ const { code: fixedByRules, fixes: ruleFixes } = applyComponentRules(fixed);
6049
+ if (ruleFixes.length > 0) {
6050
+ fixed = fixedByRules;
6051
+ fixes.push(...ruleFixes);
6052
+ }
5922
6053
  fixed = fixed.replace(/className="([^"]*)"/g, (_match, inner) => {
5923
6054
  const cleaned = inner.replace(/\s{2,}/g, " ").trim();
5924
6055
  return `className="${cleaned}"`;
@@ -6944,7 +7075,29 @@ function extractAppNameFromPrompt(prompt) {
6944
7075
  const m = prompt.match(re);
6945
7076
  if (m && m[1] && m[1].length >= 2 && m[1].length <= 30) {
6946
7077
  const name = m[1].replace(/[.,;:!?]$/, "");
6947
- const skip = /* @__PURE__ */ new Set(["a", "an", "the", "my", "our", "new", "full", "complete", "simple", "modern", "beautiful", "responsive", "fast", "cool", "great", "basic", "quick", "small", "large", "custom", "nice"]);
7078
+ const skip = /* @__PURE__ */ new Set([
7079
+ "a",
7080
+ "an",
7081
+ "the",
7082
+ "my",
7083
+ "our",
7084
+ "new",
7085
+ "full",
7086
+ "complete",
7087
+ "simple",
7088
+ "modern",
7089
+ "beautiful",
7090
+ "responsive",
7091
+ "fast",
7092
+ "cool",
7093
+ "great",
7094
+ "basic",
7095
+ "quick",
7096
+ "small",
7097
+ "large",
7098
+ "custom",
7099
+ "nice"
7100
+ ]);
6948
7101
  if (skip.has(name.toLowerCase())) continue;
6949
7102
  return name.charAt(0).toUpperCase() + name.slice(1);
6950
7103
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.8",
6
+ "version": "0.5.9",
7
7
  "description": "CLI interface for Coherent Design Method",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",