sonance-brand-mcp 1.3.110 → 1.3.112
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/dist/assets/api/sonance-ai-edit/route.ts +30 -7
- package/dist/assets/api/sonance-save-image/route.ts +625 -0
- package/dist/assets/api/sonance-vision-apply/image-styling-detection.ts +1360 -0
- package/dist/assets/api/sonance-vision-apply/route.ts +1020 -64
- package/dist/assets/api/sonance-vision-apply/styling-detection.ts +730 -0
- package/dist/assets/api/sonance-vision-apply/theme-discovery.ts +1 -1
- package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
- package/dist/assets/brand-system.ts +13 -12
- package/dist/assets/components/accordion.tsx +15 -7
- package/dist/assets/components/alert-dialog.tsx +35 -10
- package/dist/assets/components/alert.tsx +11 -10
- package/dist/assets/components/avatar.tsx +4 -4
- package/dist/assets/components/badge.tsx +16 -12
- package/dist/assets/components/button.stories.tsx +3 -3
- package/dist/assets/components/button.tsx +50 -31
- package/dist/assets/components/calendar.tsx +12 -8
- package/dist/assets/components/card.tsx +35 -29
- package/dist/assets/components/checkbox.tsx +9 -8
- package/dist/assets/components/code.tsx +19 -11
- package/dist/assets/components/command.tsx +32 -13
- package/dist/assets/components/context-menu.tsx +37 -16
- package/dist/assets/components/dialog.tsx +8 -5
- package/dist/assets/components/divider.tsx +15 -5
- package/dist/assets/components/drawer.tsx +4 -3
- package/dist/assets/components/dropdown-menu.tsx +15 -13
- package/dist/assets/components/hover-card.tsx +4 -1
- package/dist/assets/components/image.tsx +1 -1
- package/dist/assets/components/input.tsx +29 -14
- package/dist/assets/components/kbd.stories.tsx +3 -3
- package/dist/assets/components/kbd.tsx +29 -13
- package/dist/assets/components/listbox.tsx +8 -8
- package/dist/assets/components/menubar.tsx +50 -23
- package/dist/assets/components/navbar.stories.tsx +140 -13
- package/dist/assets/components/navbar.tsx +22 -5
- package/dist/assets/components/navigation-menu.tsx +28 -6
- package/dist/assets/components/pagination.tsx +10 -10
- package/dist/assets/components/popover.tsx +10 -8
- package/dist/assets/components/progress.tsx +6 -4
- package/dist/assets/components/radio-group.tsx +5 -5
- package/dist/assets/components/select.tsx +49 -29
- package/dist/assets/components/separator.tsx +3 -3
- package/dist/assets/components/sheet.tsx +4 -4
- package/dist/assets/components/sidebar.tsx +10 -10
- package/dist/assets/components/skeleton.tsx +13 -5
- package/dist/assets/components/slider.tsx +12 -10
- package/dist/assets/components/switch.tsx +4 -4
- package/dist/assets/components/table.tsx +5 -5
- package/dist/assets/components/tabs.tsx +8 -8
- package/dist/assets/components/textarea.tsx +11 -9
- package/dist/assets/components/toast.tsx +7 -7
- package/dist/assets/components/toggle.tsx +27 -7
- package/dist/assets/components/tooltip.tsx +10 -8
- package/dist/assets/components/user.tsx +8 -6
- package/dist/assets/dev-tools/SonanceDevTools.tsx +851 -708
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
- package/dist/assets/dev-tools/components/ChatHistory.tsx +145 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +444 -295
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
- package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +528 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +21 -18
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +1345 -0
- package/dist/assets/dev-tools/components/ScreenshotAnnotator.tsx +1 -1
- package/dist/assets/dev-tools/components/SectionHighlight.tsx +1 -1
- package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +7 -7
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +12 -63
- package/dist/assets/dev-tools/constants.ts +38 -6
- package/dist/assets/dev-tools/hooks/index.ts +69 -0
- package/dist/assets/dev-tools/hooks/useComponentDetection.ts +132 -0
- package/dist/assets/dev-tools/hooks/useComputedStyles.ts +471 -0
- package/dist/assets/dev-tools/hooks/useContentHash.ts +212 -0
- package/dist/assets/dev-tools/hooks/useElementScanner.ts +398 -0
- package/dist/assets/dev-tools/hooks/useImageDetection.ts +162 -0
- package/dist/assets/dev-tools/hooks/useTextDetection.ts +217 -0
- package/dist/assets/dev-tools/index.ts +3 -0
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +384 -131
- package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
- package/dist/assets/dev-tools/types.ts +93 -2
- package/dist/assets/globals.css +225 -9
- package/dist/assets/styles/brand-overrides.css +3 -2
- package/dist/assets/utils.ts +2 -1
- package/dist/index.js +22 -3
- package/package.json +2 -1
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styling System Detection
|
|
3
|
+
*
|
|
4
|
+
* Analyzes a codebase to determine what styling approach is used,
|
|
5
|
+
* enabling the AI to generate appropriate code modifications.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
|
|
11
|
+
export type StylingSystem =
|
|
12
|
+
| "tailwind"
|
|
13
|
+
| "css-modules"
|
|
14
|
+
| "styled-components"
|
|
15
|
+
| "emotion"
|
|
16
|
+
| "vanilla-css"
|
|
17
|
+
| "scss"
|
|
18
|
+
| "sass"
|
|
19
|
+
| "less"
|
|
20
|
+
| "inline-styles"
|
|
21
|
+
| "unknown";
|
|
22
|
+
|
|
23
|
+
export type ComponentLibrary =
|
|
24
|
+
| "cva" // class-variance-authority
|
|
25
|
+
| "shadcn"
|
|
26
|
+
| "radix"
|
|
27
|
+
| "chakra"
|
|
28
|
+
| "material-ui"
|
|
29
|
+
| "ant-design"
|
|
30
|
+
| "none";
|
|
31
|
+
|
|
32
|
+
export interface StylingAnalysis {
|
|
33
|
+
primarySystem: StylingSystem;
|
|
34
|
+
secondarySystems: StylingSystem[];
|
|
35
|
+
componentLibraries: ComponentLibrary[];
|
|
36
|
+
hasClassMerging: boolean; // e.g., tailwind-merge, clsx
|
|
37
|
+
hasCSSVariables: boolean;
|
|
38
|
+
hasDesignTokens: boolean;
|
|
39
|
+
confidence: number; // 0-1
|
|
40
|
+
evidence: {
|
|
41
|
+
files: string[];
|
|
42
|
+
patterns: string[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface DetectionRule {
|
|
47
|
+
system: StylingSystem | ComponentLibrary;
|
|
48
|
+
type: "styling" | "component";
|
|
49
|
+
// Package.json dependencies to check
|
|
50
|
+
dependencies?: string[];
|
|
51
|
+
// File patterns to look for
|
|
52
|
+
filePatterns?: RegExp[];
|
|
53
|
+
// Code patterns to detect in files
|
|
54
|
+
codePatterns?: RegExp[];
|
|
55
|
+
// Config files that indicate this system
|
|
56
|
+
configFiles?: string[];
|
|
57
|
+
weight: number; // Higher = more definitive
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const DETECTION_RULES: DetectionRule[] = [
|
|
61
|
+
// Tailwind CSS
|
|
62
|
+
{
|
|
63
|
+
system: "tailwind",
|
|
64
|
+
type: "styling",
|
|
65
|
+
dependencies: ["tailwindcss"],
|
|
66
|
+
configFiles: ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs"],
|
|
67
|
+
codePatterns: [
|
|
68
|
+
/className\s*=\s*["'`][^"'`]*(?:flex|grid|p-\d|m-\d|text-|bg-|border-|rounded)/,
|
|
69
|
+
/\bclass(?:Name)?\s*[:=]\s*["'`](?:[a-z]+-)+[a-z]+/,
|
|
70
|
+
],
|
|
71
|
+
weight: 10,
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// CSS Modules
|
|
75
|
+
{
|
|
76
|
+
system: "css-modules",
|
|
77
|
+
type: "styling",
|
|
78
|
+
filePatterns: [/\.module\.css$/, /\.module\.scss$/, /\.module\.sass$/],
|
|
79
|
+
codePatterns: [
|
|
80
|
+
/import\s+(?:styles|css|s)\s+from\s+["'][^"']+\.module\.(css|scss|sass)["']/,
|
|
81
|
+
/className\s*=\s*\{?\s*styles\./,
|
|
82
|
+
],
|
|
83
|
+
weight: 9,
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// styled-components
|
|
87
|
+
{
|
|
88
|
+
system: "styled-components",
|
|
89
|
+
type: "styling",
|
|
90
|
+
dependencies: ["styled-components"],
|
|
91
|
+
codePatterns: [
|
|
92
|
+
/import\s+styled\s+from\s+["']styled-components["']/,
|
|
93
|
+
/styled\.[a-z]+`/,
|
|
94
|
+
/styled\([A-Z][a-zA-Z]*\)`/,
|
|
95
|
+
],
|
|
96
|
+
weight: 10,
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// Emotion
|
|
100
|
+
{
|
|
101
|
+
system: "emotion",
|
|
102
|
+
type: "styling",
|
|
103
|
+
dependencies: ["@emotion/react", "@emotion/styled", "@emotion/css"],
|
|
104
|
+
codePatterns: [
|
|
105
|
+
/import\s+.*from\s+["']@emotion\/(react|styled|css)["']/,
|
|
106
|
+
/css\s*`/,
|
|
107
|
+
/styled\.[a-z]+`/,
|
|
108
|
+
],
|
|
109
|
+
weight: 10,
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
// SCSS/Sass
|
|
113
|
+
{
|
|
114
|
+
system: "scss",
|
|
115
|
+
type: "styling",
|
|
116
|
+
dependencies: ["sass", "node-sass"],
|
|
117
|
+
filePatterns: [/\.scss$/],
|
|
118
|
+
codePatterns: [/import\s+["'][^"']+\.scss["']/],
|
|
119
|
+
weight: 7,
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// Less
|
|
123
|
+
{
|
|
124
|
+
system: "less",
|
|
125
|
+
type: "styling",
|
|
126
|
+
dependencies: ["less"],
|
|
127
|
+
filePatterns: [/\.less$/],
|
|
128
|
+
codePatterns: [/import\s+["'][^"']+\.less["']/],
|
|
129
|
+
weight: 7,
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// CVA (class-variance-authority)
|
|
133
|
+
{
|
|
134
|
+
system: "cva",
|
|
135
|
+
type: "component",
|
|
136
|
+
dependencies: ["class-variance-authority"],
|
|
137
|
+
codePatterns: [
|
|
138
|
+
/import\s+.*\bcva\b.*from\s+["']class-variance-authority["']/,
|
|
139
|
+
/\bcva\s*\(/,
|
|
140
|
+
],
|
|
141
|
+
weight: 8,
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
// shadcn/ui
|
|
145
|
+
{
|
|
146
|
+
system: "shadcn",
|
|
147
|
+
type: "component",
|
|
148
|
+
configFiles: ["components.json"],
|
|
149
|
+
filePatterns: [/components\/ui\//],
|
|
150
|
+
codePatterns: [
|
|
151
|
+
/@\/components\/ui\//,
|
|
152
|
+
/from\s+["']@\/components\/ui\//,
|
|
153
|
+
],
|
|
154
|
+
weight: 8,
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
// Radix UI
|
|
158
|
+
{
|
|
159
|
+
system: "radix",
|
|
160
|
+
type: "component",
|
|
161
|
+
dependencies: ["@radix-ui/react-dialog", "@radix-ui/react-dropdown-menu", "@radix-ui/react-popover"],
|
|
162
|
+
codePatterns: [/import\s+.*from\s+["']@radix-ui\//],
|
|
163
|
+
weight: 7,
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Chakra UI
|
|
167
|
+
{
|
|
168
|
+
system: "chakra",
|
|
169
|
+
type: "component",
|
|
170
|
+
dependencies: ["@chakra-ui/react"],
|
|
171
|
+
codePatterns: [/import\s+.*from\s+["']@chakra-ui\//],
|
|
172
|
+
weight: 9,
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// Material UI
|
|
176
|
+
{
|
|
177
|
+
system: "material-ui",
|
|
178
|
+
type: "component",
|
|
179
|
+
dependencies: ["@mui/material", "@material-ui/core"],
|
|
180
|
+
codePatterns: [
|
|
181
|
+
/import\s+.*from\s+["']@mui\//,
|
|
182
|
+
/import\s+.*from\s+["']@material-ui\//,
|
|
183
|
+
],
|
|
184
|
+
weight: 9,
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Ant Design
|
|
188
|
+
{
|
|
189
|
+
system: "ant-design",
|
|
190
|
+
type: "component",
|
|
191
|
+
dependencies: ["antd"],
|
|
192
|
+
codePatterns: [/import\s+.*from\s+["']antd["']/],
|
|
193
|
+
weight: 9,
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
// Patterns that indicate class merging utilities
|
|
198
|
+
const CLASS_MERGING_PATTERNS = [
|
|
199
|
+
/import\s+.*\btwMerge\b.*from\s+["']tailwind-merge["']/,
|
|
200
|
+
/import\s+.*\bclsx\b.*from\s+["']clsx["']/,
|
|
201
|
+
/import\s+.*\bclassnames\b.*from\s+["']classnames["']/,
|
|
202
|
+
/import\s+.*\bcn\b.*from/,
|
|
203
|
+
/export\s+function\s+cn\s*\(/,
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
// Patterns that indicate CSS variables / design tokens
|
|
207
|
+
const CSS_VARIABLES_PATTERNS = [
|
|
208
|
+
/--[a-z][a-z0-9-]*\s*:/,
|
|
209
|
+
/var\s*\(\s*--[a-z]/,
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
const DESIGN_TOKENS_PATTERNS = [
|
|
213
|
+
/tokens?\.(colors?|spacing|typography)/i,
|
|
214
|
+
/theme\.(colors?|spacing|typography)/i,
|
|
215
|
+
/"tokens"\s*:/,
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Analyze a codebase to detect the styling system(s) in use
|
|
220
|
+
*/
|
|
221
|
+
export async function detectStylingSystem(projectRoot: string): Promise<StylingAnalysis> {
|
|
222
|
+
const result: StylingAnalysis = {
|
|
223
|
+
primarySystem: "unknown",
|
|
224
|
+
secondarySystems: [],
|
|
225
|
+
componentLibraries: [],
|
|
226
|
+
hasClassMerging: false,
|
|
227
|
+
hasCSSVariables: false,
|
|
228
|
+
hasDesignTokens: false,
|
|
229
|
+
confidence: 0,
|
|
230
|
+
evidence: {
|
|
231
|
+
files: [],
|
|
232
|
+
patterns: [],
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Track scores for each system
|
|
237
|
+
const systemScores: Record<string, number> = {};
|
|
238
|
+
const componentScores: Record<string, number> = {};
|
|
239
|
+
|
|
240
|
+
// 1. Check package.json
|
|
241
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
242
|
+
let packageJson: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> } = {};
|
|
243
|
+
|
|
244
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
245
|
+
try {
|
|
246
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
247
|
+
const allDeps = {
|
|
248
|
+
...packageJson.dependencies,
|
|
249
|
+
...packageJson.devDependencies,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
for (const rule of DETECTION_RULES) {
|
|
253
|
+
if (rule.dependencies) {
|
|
254
|
+
for (const dep of rule.dependencies) {
|
|
255
|
+
if (allDeps[dep]) {
|
|
256
|
+
const scoreMap = rule.type === "styling" ? systemScores : componentScores;
|
|
257
|
+
scoreMap[rule.system] = (scoreMap[rule.system] || 0) + rule.weight;
|
|
258
|
+
result.evidence.patterns.push(`package.json: ${dep}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check for class merging utilities
|
|
265
|
+
if (allDeps["tailwind-merge"] || allDeps["clsx"] || allDeps["classnames"]) {
|
|
266
|
+
result.hasClassMerging = true;
|
|
267
|
+
result.evidence.patterns.push("Class merging utility detected");
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
// Ignore JSON parse errors
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 2. Check for config files
|
|
275
|
+
for (const rule of DETECTION_RULES) {
|
|
276
|
+
if (rule.configFiles) {
|
|
277
|
+
for (const configFile of rule.configFiles) {
|
|
278
|
+
const configPath = path.join(projectRoot, configFile);
|
|
279
|
+
if (fs.existsSync(configPath)) {
|
|
280
|
+
const scoreMap = rule.type === "styling" ? systemScores : componentScores;
|
|
281
|
+
scoreMap[rule.system] = (scoreMap[rule.system] || 0) + rule.weight;
|
|
282
|
+
result.evidence.files.push(configFile);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 3. Scan source files for patterns
|
|
289
|
+
const srcDir = path.join(projectRoot, "src");
|
|
290
|
+
if (fs.existsSync(srcDir)) {
|
|
291
|
+
const filesToScan = findFilesRecursive(srcDir, /\.(tsx?|jsx?|css|scss|sass|less)$/, 100);
|
|
292
|
+
|
|
293
|
+
for (const filePath of filesToScan) {
|
|
294
|
+
try {
|
|
295
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
296
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
297
|
+
|
|
298
|
+
// Check code patterns
|
|
299
|
+
for (const rule of DETECTION_RULES) {
|
|
300
|
+
// Check file pattern matches
|
|
301
|
+
if (rule.filePatterns) {
|
|
302
|
+
for (const pattern of rule.filePatterns) {
|
|
303
|
+
if (pattern.test(filePath)) {
|
|
304
|
+
const scoreMap = rule.type === "styling" ? systemScores : componentScores;
|
|
305
|
+
scoreMap[rule.system] = (scoreMap[rule.system] || 0) + (rule.weight / 2);
|
|
306
|
+
if (!result.evidence.files.includes(relativePath)) {
|
|
307
|
+
result.evidence.files.push(relativePath);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Check code patterns
|
|
314
|
+
if (rule.codePatterns) {
|
|
315
|
+
for (const pattern of rule.codePatterns) {
|
|
316
|
+
if (pattern.test(content)) {
|
|
317
|
+
const scoreMap = rule.type === "styling" ? systemScores : componentScores;
|
|
318
|
+
scoreMap[rule.system] = (scoreMap[rule.system] || 0) + (rule.weight / 3);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check for CSS variables
|
|
325
|
+
for (const pattern of CSS_VARIABLES_PATTERNS) {
|
|
326
|
+
if (pattern.test(content)) {
|
|
327
|
+
result.hasCSSVariables = true;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Check for design tokens
|
|
333
|
+
for (const pattern of DESIGN_TOKENS_PATTERNS) {
|
|
334
|
+
if (pattern.test(content)) {
|
|
335
|
+
result.hasDesignTokens = true;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check for class merging
|
|
341
|
+
if (!result.hasClassMerging) {
|
|
342
|
+
for (const pattern of CLASS_MERGING_PATTERNS) {
|
|
343
|
+
if (pattern.test(content)) {
|
|
344
|
+
result.hasClassMerging = true;
|
|
345
|
+
result.evidence.patterns.push(`Class merging in ${relativePath}`);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
// Ignore file read errors
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 4. Determine primary and secondary systems
|
|
357
|
+
const sortedSystems = Object.entries(systemScores)
|
|
358
|
+
.sort(([, a], [, b]) => b - a)
|
|
359
|
+
.map(([system]) => system as StylingSystem);
|
|
360
|
+
|
|
361
|
+
if (sortedSystems.length > 0) {
|
|
362
|
+
result.primarySystem = sortedSystems[0];
|
|
363
|
+
result.secondarySystems = sortedSystems.slice(1);
|
|
364
|
+
|
|
365
|
+
// Calculate confidence based on score difference
|
|
366
|
+
const topScore = systemScores[sortedSystems[0]] || 0;
|
|
367
|
+
const secondScore = sortedSystems[1] ? systemScores[sortedSystems[1]] || 0 : 0;
|
|
368
|
+
result.confidence = Math.min(1, topScore / 30 + (topScore - secondScore) / 20);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Add component libraries
|
|
372
|
+
result.componentLibraries = Object.entries(componentScores)
|
|
373
|
+
.filter(([, score]) => score > 5)
|
|
374
|
+
.sort(([, a], [, b]) => b - a)
|
|
375
|
+
.map(([lib]) => lib as ComponentLibrary);
|
|
376
|
+
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Generate styling-specific AI guidance based on detected system
|
|
382
|
+
*/
|
|
383
|
+
export function generateStylingGuidance(analysis: StylingAnalysis): string {
|
|
384
|
+
const sections: string[] = [];
|
|
385
|
+
|
|
386
|
+
sections.push(`
|
|
387
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
388
|
+
DETECTED STYLING SYSTEM: ${analysis.primarySystem.toUpperCase()}
|
|
389
|
+
Confidence: ${Math.round(analysis.confidence * 100)}%
|
|
390
|
+
${analysis.secondarySystems.length > 0 ? `Also uses: ${analysis.secondarySystems.join(", ")}` : ""}
|
|
391
|
+
${analysis.componentLibraries.length > 0 ? `Component libraries: ${analysis.componentLibraries.join(", ")}` : ""}
|
|
392
|
+
═══════════════════════════════════════════════════════════════════════════════`);
|
|
393
|
+
|
|
394
|
+
// System-specific guidance
|
|
395
|
+
switch (analysis.primarySystem) {
|
|
396
|
+
case "tailwind":
|
|
397
|
+
sections.push(generateTailwindGuidance(analysis));
|
|
398
|
+
break;
|
|
399
|
+
case "css-modules":
|
|
400
|
+
sections.push(generateCSSModulesGuidance());
|
|
401
|
+
break;
|
|
402
|
+
case "styled-components":
|
|
403
|
+
case "emotion":
|
|
404
|
+
sections.push(generateCSSInJSGuidance(analysis.primarySystem));
|
|
405
|
+
break;
|
|
406
|
+
case "scss":
|
|
407
|
+
case "sass":
|
|
408
|
+
case "less":
|
|
409
|
+
sections.push(generatePreprocessorGuidance(analysis.primarySystem));
|
|
410
|
+
break;
|
|
411
|
+
case "vanilla-css":
|
|
412
|
+
sections.push(generateVanillaCSSGuidance());
|
|
413
|
+
break;
|
|
414
|
+
default:
|
|
415
|
+
sections.push(generateGenericGuidance());
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Component library specific guidance
|
|
419
|
+
for (const lib of analysis.componentLibraries) {
|
|
420
|
+
sections.push(generateComponentLibraryGuidance(lib, analysis));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return sections.join("\n\n");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function generateTailwindGuidance(analysis: StylingAnalysis): string {
|
|
427
|
+
let guidance = `
|
|
428
|
+
TAILWIND CSS MODIFICATION RULES:
|
|
429
|
+
|
|
430
|
+
1. MODIFYING STYLES:
|
|
431
|
+
- Add/change utility classes in className prop
|
|
432
|
+
- Example: className="bg-blue-500" → className="bg-green-500"
|
|
433
|
+
|
|
434
|
+
2. SPACING: Use Tailwind spacing scale
|
|
435
|
+
- Padding: p-{0-96}, px-{}, py-{}, pt-{}, etc.
|
|
436
|
+
- Margin: m-{0-96}, mx-{}, my-{}, mt-{}, etc.
|
|
437
|
+
- Gap: gap-{0-96}
|
|
438
|
+
|
|
439
|
+
3. COLORS: Use theme colors or arbitrary values
|
|
440
|
+
- Theme: bg-primary, text-foreground, border-border
|
|
441
|
+
- Arbitrary: bg-[#FF5733], text-[rgb(255,87,51)]
|
|
442
|
+
|
|
443
|
+
4. LAYOUT:
|
|
444
|
+
- Flexbox: flex, items-center, justify-between, flex-col
|
|
445
|
+
- Grid: grid, grid-cols-{1-12}, gap-{n}
|
|
446
|
+
|
|
447
|
+
5. SIZING:
|
|
448
|
+
- Width: w-{size}, w-full, w-screen, w-[200px]
|
|
449
|
+
- Height: h-{size}, h-full, h-screen, h-[200px]
|
|
450
|
+
- Border radius: rounded-{none|sm|md|lg|xl|2xl|full}`;
|
|
451
|
+
|
|
452
|
+
if (analysis.hasClassMerging) {
|
|
453
|
+
guidance += `
|
|
454
|
+
|
|
455
|
+
6. CLASS MERGING ENABLED:
|
|
456
|
+
- This codebase uses a class merging utility (cn/clsx/twMerge)
|
|
457
|
+
- Later classes WILL override earlier conflicting classes
|
|
458
|
+
- Safe to add override classes: className="rounded-none" will override component defaults`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (analysis.hasCSSVariables) {
|
|
462
|
+
guidance += `
|
|
463
|
+
|
|
464
|
+
7. CSS VARIABLES AVAILABLE:
|
|
465
|
+
- Use semantic color variables when available
|
|
466
|
+
- Prefer var(--primary) over hardcoded colors
|
|
467
|
+
- Check globals.css or theme files for available variables`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return guidance;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function generateCSSModulesGuidance(): string {
|
|
474
|
+
return `
|
|
475
|
+
CSS MODULES MODIFICATION RULES:
|
|
476
|
+
|
|
477
|
+
1. STYLE CHANGES REQUIRE EDITING .module.css FILES:
|
|
478
|
+
- Find the imported CSS module (e.g., styles from './Component.module.css')
|
|
479
|
+
- Modify the CSS class definitions in that file
|
|
480
|
+
|
|
481
|
+
2. TO ADD NEW STYLES:
|
|
482
|
+
- Add new class to the .module.css file
|
|
483
|
+
- Reference in component: className={styles.newClassName}
|
|
484
|
+
|
|
485
|
+
3. TO MODIFY EXISTING STYLES:
|
|
486
|
+
- Locate the class in the .module.css file
|
|
487
|
+
- Update the CSS properties there
|
|
488
|
+
|
|
489
|
+
4. COMPOSING STYLES:
|
|
490
|
+
- Use composes: existingClass; to extend styles
|
|
491
|
+
- Or combine: className={\`\${styles.base} \${styles.modifier}\`}
|
|
492
|
+
|
|
493
|
+
5. STRUCTURAL VS DESIGN CHANGES:
|
|
494
|
+
- Design changes: Edit the .module.css file
|
|
495
|
+
- Structural changes: Edit the component JSX
|
|
496
|
+
|
|
497
|
+
EXAMPLE:
|
|
498
|
+
User asks: "make the card blue"
|
|
499
|
+
1. Find: import styles from './Card.module.css'
|
|
500
|
+
2. Edit Card.module.css: .card { background-color: blue; }
|
|
501
|
+
NOT: Add inline styles or className strings`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function generateCSSInJSGuidance(system: "styled-components" | "emotion"): string {
|
|
505
|
+
const importPath = system === "styled-components" ? "styled-components" : "@emotion/styled";
|
|
506
|
+
|
|
507
|
+
return `
|
|
508
|
+
${system.toUpperCase()} MODIFICATION RULES:
|
|
509
|
+
|
|
510
|
+
1. MODIFYING STYLED COMPONENTS:
|
|
511
|
+
- Find the styled.div\` ... \` or styled(Component)\` ... \` definition
|
|
512
|
+
- Modify the CSS inside the template literal
|
|
513
|
+
|
|
514
|
+
2. EXAMPLE:
|
|
515
|
+
const Card = styled.div\`
|
|
516
|
+
background: blue; // Change this value
|
|
517
|
+
padding: 16px;
|
|
518
|
+
\`;
|
|
519
|
+
|
|
520
|
+
3. DYNAMIC STYLES (props-based):
|
|
521
|
+
const Button = styled.button\`
|
|
522
|
+
background: \${props => props.primary ? 'blue' : 'gray'};
|
|
523
|
+
\`;
|
|
524
|
+
// Pass props: <Button primary />
|
|
525
|
+
|
|
526
|
+
4. EXTENDING STYLES:
|
|
527
|
+
const BlueCard = styled(Card)\`
|
|
528
|
+
background: blue;
|
|
529
|
+
\`;
|
|
530
|
+
|
|
531
|
+
5. TO CHANGE COMPONENT INSTANCES:
|
|
532
|
+
- Prefer extending with styled() over inline overrides
|
|
533
|
+
- Or use the css prop: <Card css={{ background: 'blue' }} />
|
|
534
|
+
|
|
535
|
+
DESIGN CHANGES: Edit the styled\` \` template
|
|
536
|
+
STRUCTURAL CHANGES: Edit the JSX structure`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function generatePreprocessorGuidance(system: "scss" | "sass" | "less"): string {
|
|
540
|
+
const extension = system === "less" ? "less" : system;
|
|
541
|
+
|
|
542
|
+
return `
|
|
543
|
+
${system.toUpperCase()} MODIFICATION RULES:
|
|
544
|
+
|
|
545
|
+
1. STYLE CHANGES GO IN .${extension} FILES:
|
|
546
|
+
- Find the imported stylesheet
|
|
547
|
+
- Modify CSS/SCSS rules there
|
|
548
|
+
|
|
549
|
+
2. USING VARIABLES:
|
|
550
|
+
- SCSS/Sass: $variable-name
|
|
551
|
+
- Less: @variable-name
|
|
552
|
+
- Modify variable definitions for global changes
|
|
553
|
+
|
|
554
|
+
3. NESTING:
|
|
555
|
+
- Styles are often nested under parent selectors
|
|
556
|
+
- Follow the nesting to find the right rule
|
|
557
|
+
|
|
558
|
+
4. MIXINS:
|
|
559
|
+
- Look for @mixin/@include (SCSS) or .mixin() (Less)
|
|
560
|
+
- Modify mixin definitions for reusable style changes
|
|
561
|
+
|
|
562
|
+
5. APPROACH:
|
|
563
|
+
- Design changes: Edit .${extension} files
|
|
564
|
+
- Structural changes: Edit component files
|
|
565
|
+
- Use variables for theme-wide changes`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function generateVanillaCSSGuidance(): string {
|
|
569
|
+
return `
|
|
570
|
+
VANILLA CSS MODIFICATION RULES:
|
|
571
|
+
|
|
572
|
+
1. FIND THE CSS FILE:
|
|
573
|
+
- Check for imported .css files
|
|
574
|
+
- Or look for <link> tags in HTML/layout files
|
|
575
|
+
|
|
576
|
+
2. CSS SPECIFICITY MATTERS:
|
|
577
|
+
- More specific selectors override less specific ones
|
|
578
|
+
- ID > Class > Element
|
|
579
|
+
- Later rules override earlier ones (same specificity)
|
|
580
|
+
|
|
581
|
+
3. TO CHANGE STYLES:
|
|
582
|
+
- Locate the selector in the CSS file
|
|
583
|
+
- Modify the properties
|
|
584
|
+
- Consider specificity when adding new rules
|
|
585
|
+
|
|
586
|
+
4. FOR ONE-OFF CHANGES:
|
|
587
|
+
- Add inline styles: style={{ backgroundColor: 'blue' }}
|
|
588
|
+
- Or add a more specific class selector
|
|
589
|
+
|
|
590
|
+
5. STRUCTURAL VS DESIGN:
|
|
591
|
+
- Design: Edit CSS files
|
|
592
|
+
- Structural: Edit JSX/HTML`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function generateGenericGuidance(): string {
|
|
596
|
+
return `
|
|
597
|
+
STYLING APPROACH NOT DEFINITIVELY DETECTED
|
|
598
|
+
|
|
599
|
+
When modifying styles, consider:
|
|
600
|
+
|
|
601
|
+
1. CHECK FOR:
|
|
602
|
+
- className props (utility classes or CSS class references)
|
|
603
|
+
- style={{ }} inline styles
|
|
604
|
+
- styled-components or CSS-in-JS
|
|
605
|
+
- External CSS file imports
|
|
606
|
+
|
|
607
|
+
2. FOLLOW THE PATTERN:
|
|
608
|
+
- Look at how existing styles are applied
|
|
609
|
+
- Match the same approach for consistency
|
|
610
|
+
|
|
611
|
+
3. COMMON PATTERNS:
|
|
612
|
+
- className="..." → Add/modify class names
|
|
613
|
+
- style={{...}} → Modify inline style object
|
|
614
|
+
- styled.div\`\` → Edit template literal CSS
|
|
615
|
+
- import './styles.css' → Edit the CSS file`;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function generateComponentLibraryGuidance(library: ComponentLibrary, analysis: StylingAnalysis): string {
|
|
619
|
+
switch (library) {
|
|
620
|
+
case "cva":
|
|
621
|
+
return `
|
|
622
|
+
CVA (class-variance-authority) DETECTED:
|
|
623
|
+
|
|
624
|
+
Components use variants for predefined styles:
|
|
625
|
+
- size="sm" | "md" | "lg"
|
|
626
|
+
- variant="default" | "destructive" | "outline"
|
|
627
|
+
|
|
628
|
+
TO MODIFY:
|
|
629
|
+
1. Use variant props when available
|
|
630
|
+
2. Override with className (${analysis.hasClassMerging ? "class merging is enabled" : "may need careful ordering"})
|
|
631
|
+
3. Edit the cva() definition for permanent changes
|
|
632
|
+
|
|
633
|
+
Example:
|
|
634
|
+
<Button variant="destructive" size="lg"> // Use props
|
|
635
|
+
<Button className="rounded-full"> // Override`;
|
|
636
|
+
|
|
637
|
+
case "shadcn":
|
|
638
|
+
return `
|
|
639
|
+
SHADCN/UI DETECTED:
|
|
640
|
+
|
|
641
|
+
Components are in src/components/ui/ and can be modified directly.
|
|
642
|
+
They typically use:
|
|
643
|
+
- CVA for variants
|
|
644
|
+
- Tailwind for styling
|
|
645
|
+
- Radix primitives for behavior
|
|
646
|
+
|
|
647
|
+
TO MODIFY:
|
|
648
|
+
1. Use component props (variant, size, etc.)
|
|
649
|
+
2. Pass className for overrides
|
|
650
|
+
3. Edit the component file for permanent changes`;
|
|
651
|
+
|
|
652
|
+
case "chakra":
|
|
653
|
+
return `
|
|
654
|
+
CHAKRA UI DETECTED:
|
|
655
|
+
|
|
656
|
+
Use Chakra's style props directly on components:
|
|
657
|
+
<Box bg="blue.500" p={4} borderRadius="lg">
|
|
658
|
+
<Button colorScheme="blue" size="lg">
|
|
659
|
+
|
|
660
|
+
TO MODIFY:
|
|
661
|
+
1. Use style props: bg, color, p, m, w, h, etc.
|
|
662
|
+
2. Use theme tokens: "blue.500", "gray.100"
|
|
663
|
+
3. Use sx prop for custom CSS: sx={{ '&:hover': { bg: 'blue.600' } }}`;
|
|
664
|
+
|
|
665
|
+
case "material-ui":
|
|
666
|
+
return `
|
|
667
|
+
MATERIAL UI DETECTED:
|
|
668
|
+
|
|
669
|
+
Use MUI's sx prop or styled() API:
|
|
670
|
+
<Box sx={{ bgcolor: 'primary.main', p: 2 }}>
|
|
671
|
+
<Button variant="contained" color="primary">
|
|
672
|
+
|
|
673
|
+
TO MODIFY:
|
|
674
|
+
1. Use sx prop for one-off styles
|
|
675
|
+
2. Use theme tokens: 'primary.main', 'grey.100'
|
|
676
|
+
3. Use styled() API for reusable styled components`;
|
|
677
|
+
|
|
678
|
+
case "ant-design":
|
|
679
|
+
return `
|
|
680
|
+
ANT DESIGN DETECTED:
|
|
681
|
+
|
|
682
|
+
Components use props for variants:
|
|
683
|
+
<Button type="primary" size="large">
|
|
684
|
+
<Card bordered={false}>
|
|
685
|
+
|
|
686
|
+
TO MODIFY:
|
|
687
|
+
1. Use component props (type, size, etc.)
|
|
688
|
+
2. Use style prop for custom inline styles
|
|
689
|
+
3. Use className for CSS class overrides
|
|
690
|
+
4. Modify theme variables for global changes`;
|
|
691
|
+
|
|
692
|
+
default:
|
|
693
|
+
return "";
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Helper to recursively find files
|
|
699
|
+
*/
|
|
700
|
+
function findFilesRecursive(dir: string, pattern: RegExp, maxFiles: number): string[] {
|
|
701
|
+
const files: string[] = [];
|
|
702
|
+
|
|
703
|
+
function walk(currentDir: string) {
|
|
704
|
+
if (files.length >= maxFiles) return;
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
708
|
+
|
|
709
|
+
for (const entry of entries) {
|
|
710
|
+
if (files.length >= maxFiles) break;
|
|
711
|
+
|
|
712
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
713
|
+
|
|
714
|
+
// Skip node_modules, .git, dist, build, etc.
|
|
715
|
+
if (entry.isDirectory()) {
|
|
716
|
+
if (!["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
|
|
717
|
+
walk(fullPath);
|
|
718
|
+
}
|
|
719
|
+
} else if (entry.isFile() && pattern.test(entry.name)) {
|
|
720
|
+
files.push(fullPath);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
} catch {
|
|
724
|
+
// Ignore permission errors
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
walk(dir);
|
|
729
|
+
return files;
|
|
730
|
+
}
|