sonance-brand-mcp 1.3.50 → 1.3.53
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.
|
@@ -3,6 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import Anthropic from "@anthropic-ai/sdk";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
|
+
import { discoverTheme, formatThemeForPrompt } from "./theme-discovery";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Sonance DevTools API - Apply-First Vision Mode
|
|
@@ -471,10 +472,47 @@ Return search/replace patches (NOT full files). The system applies your patches
|
|
|
471
472
|
- CRITICAL: NEVER invent or guess code. Your "search" string MUST be copied EXACTLY from the provided file content. If you cannot find the exact code to modify, return an empty modifications array.
|
|
472
473
|
- If the file content appears truncated, only modify code that is visible in the provided content.
|
|
473
474
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
475
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
476
|
+
SONANCE BRAND COLOR SYSTEM
|
|
477
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
478
|
+
|
|
479
|
+
**Core Colors:**
|
|
480
|
+
- Primary (Charcoal): #333F48 - main text, dark backgrounds
|
|
481
|
+
- Accent (Cyan "The Beam"): #00D3C8 - highlights, interactive elements, CTAs
|
|
482
|
+
- Success: Green tones - positive states, confirmations
|
|
483
|
+
- Warning: Amber/Orange tones - caution states, alerts
|
|
484
|
+
- Destructive: Red tones - errors, delete actions
|
|
485
|
+
|
|
486
|
+
**CRITICAL CONTRAST RULES:**
|
|
487
|
+
When using colored backgrounds, ALWAYS use high-contrast text:
|
|
488
|
+
|
|
489
|
+
| Background | Use This Text | NEVER Use |
|
|
490
|
+
|-------------------|------------------------------------|-----------------------|
|
|
491
|
+
| bg-accent | text-white | text-accent-foreground |
|
|
492
|
+
| bg-primary | text-primary-foreground, text-white | text-primary |
|
|
493
|
+
| bg-success | text-white | text-success |
|
|
494
|
+
| bg-warning | text-white | text-warning |
|
|
495
|
+
| bg-destructive | text-white | text-destructive |
|
|
496
|
+
|
|
497
|
+
**SEMANTIC TOKEN PATTERNS:**
|
|
498
|
+
- text-{color} = the color itself (use on NEUTRAL backgrounds like white/gray)
|
|
499
|
+
- text-{color}-foreground = INTENDED for text on {color} backgrounds, but MAY have contrast issues
|
|
500
|
+
- bg-{color} = background in that color
|
|
501
|
+
- WHEN IN DOUBT: Use text-white on any colored background for guaranteed contrast
|
|
502
|
+
|
|
503
|
+
**BUTTON PATTERNS (Sonance Standard):**
|
|
504
|
+
- Primary CTA: bg-primary text-primary-foreground
|
|
505
|
+
- Accent/Highlight: bg-accent text-white (NOT text-accent-foreground)
|
|
506
|
+
- Success: bg-success text-white
|
|
507
|
+
- Warning: bg-warning text-white
|
|
508
|
+
- Destructive: bg-destructive text-white
|
|
509
|
+
- Outlined: border-border bg-transparent text-foreground
|
|
510
|
+
|
|
511
|
+
**COMMON MISTAKES TO AVOID:**
|
|
512
|
+
- WRONG: bg-accent text-accent-foreground (accent-foreground is often dark = invisible text)
|
|
513
|
+
- RIGHT: bg-accent text-white (white text on cyan = visible)
|
|
514
|
+
- WRONG: bg-primary text-primary (same color = invisible)
|
|
515
|
+
- RIGHT: bg-primary text-primary-foreground OR text-white
|
|
478
516
|
|
|
479
517
|
**RESPONSE FORMAT:**
|
|
480
518
|
CRITICAL: Return ONLY the JSON object below. Do NOT include any text, explanation, or thinking before or after the JSON. No preamble. No "Looking at the screenshot..." No markdown code blocks. Just raw JSON:
|
|
@@ -666,7 +704,7 @@ export async function POST(request: Request) {
|
|
|
666
704
|
const TOTAL_CONTEXT_BUDGET = 500000; // 500k chars total budget
|
|
667
705
|
const MAX_RECOMMENDED_FILE = Infinity; // NEVER truncate the target file - AI needs full context
|
|
668
706
|
const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
|
|
669
|
-
const MAX_GLOBALS_CSS =
|
|
707
|
+
const MAX_GLOBALS_CSS = 15000; // Increased to capture full theme definitions
|
|
670
708
|
const MAX_FILES = 25;
|
|
671
709
|
|
|
672
710
|
let usedContext = 0;
|
|
@@ -760,6 +798,25 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
|
760
798
|
}
|
|
761
799
|
}
|
|
762
800
|
|
|
801
|
+
// ========== THEME DISCOVERY ==========
|
|
802
|
+
// Dynamically discover theme tokens from the target codebase
|
|
803
|
+
const discoveredTheme = await discoverTheme(projectRoot);
|
|
804
|
+
const themeContext = formatThemeForPrompt(discoveredTheme);
|
|
805
|
+
|
|
806
|
+
if (discoveredTheme.discoveredFiles.length > 0) {
|
|
807
|
+
textContent += `
|
|
808
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
809
|
+
${themeContext}
|
|
810
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
811
|
+
|
|
812
|
+
`;
|
|
813
|
+
debugLog("Theme discovery complete", {
|
|
814
|
+
filesFound: discoveredTheme.discoveredFiles,
|
|
815
|
+
cssVariableCount: Object.keys(discoveredTheme.cssVariables).length,
|
|
816
|
+
tailwindColorCount: Object.keys(discoveredTheme.tailwindColors).length,
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
763
820
|
// ========== GLOBALS CSS ==========
|
|
764
821
|
const globalsTruncated = pageContext.globalsCSS.substring(0, MAX_GLOBALS_CSS);
|
|
765
822
|
textContent += `
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Discovery Module for Sonance DevTools
|
|
3
|
+
*
|
|
4
|
+
* Dynamically discovers and parses theme/design system files from target codebases.
|
|
5
|
+
* Extracts CSS variables, Tailwind colors, and available semantic tokens to provide
|
|
6
|
+
* context to the LLM for intelligent styling decisions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Discovered theme information from a codebase
|
|
14
|
+
*/
|
|
15
|
+
export interface DiscoveredTheme {
|
|
16
|
+
// Raw CSS variable definitions found (e.g., { "--accent": "#00D3C8" })
|
|
17
|
+
cssVariables: Record<string, string>;
|
|
18
|
+
|
|
19
|
+
// Tailwind custom colors from config (if found)
|
|
20
|
+
tailwindColors: Record<string, string>;
|
|
21
|
+
|
|
22
|
+
// Available semantic class tokens discovered
|
|
23
|
+
availableTokens: string[];
|
|
24
|
+
|
|
25
|
+
// Full raw theme CSS content for additional context
|
|
26
|
+
rawThemeCSS: string;
|
|
27
|
+
|
|
28
|
+
// Files that were discovered and parsed
|
|
29
|
+
discoveredFiles: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Patterns for finding theme-related files
|
|
34
|
+
*/
|
|
35
|
+
const THEME_FILE_PATTERNS = [
|
|
36
|
+
// CSS files
|
|
37
|
+
"src/app/globals.css",
|
|
38
|
+
"app/globals.css",
|
|
39
|
+
"src/styles/globals.css",
|
|
40
|
+
"styles/globals.css",
|
|
41
|
+
"src/styles/global.css",
|
|
42
|
+
"styles/global.css",
|
|
43
|
+
"src/styles/variables.css",
|
|
44
|
+
"styles/variables.css",
|
|
45
|
+
"src/styles/theme.css",
|
|
46
|
+
"styles/theme.css",
|
|
47
|
+
"src/styles/brand-overrides.css",
|
|
48
|
+
"styles/brand-overrides.css",
|
|
49
|
+
// Tailwind config files
|
|
50
|
+
"tailwind.config.js",
|
|
51
|
+
"tailwind.config.ts",
|
|
52
|
+
"tailwind.config.mjs",
|
|
53
|
+
"tailwind.config.cjs",
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Find all theme-related files in a project
|
|
58
|
+
*/
|
|
59
|
+
export function findThemeFiles(projectRoot: string): string[] {
|
|
60
|
+
const foundFiles: string[] = [];
|
|
61
|
+
|
|
62
|
+
for (const pattern of THEME_FILE_PATTERNS) {
|
|
63
|
+
const fullPath = path.join(projectRoot, pattern);
|
|
64
|
+
if (fs.existsSync(fullPath)) {
|
|
65
|
+
foundFiles.push(pattern);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Also search for any additional CSS files in common locations
|
|
70
|
+
const additionalDirs = ["src/styles", "styles", "src/app", "app"];
|
|
71
|
+
for (const dir of additionalDirs) {
|
|
72
|
+
const dirPath = path.join(projectRoot, dir);
|
|
73
|
+
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
|
|
74
|
+
try {
|
|
75
|
+
const files = fs.readdirSync(dirPath);
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
if (file.endsWith(".css") && !foundFiles.includes(path.join(dir, file))) {
|
|
78
|
+
foundFiles.push(path.join(dir, file));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Ignore read errors
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return foundFiles;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse CSS content and extract variable definitions
|
|
92
|
+
* Extracts patterns like: --primary: #333F48; or --accent: hsl(180 100% 50%);
|
|
93
|
+
*/
|
|
94
|
+
export function parseCSSVariables(cssContent: string): Record<string, string> {
|
|
95
|
+
const variables: Record<string, string> = {};
|
|
96
|
+
|
|
97
|
+
// Match CSS custom property definitions
|
|
98
|
+
// Handles: --name: value; with various value formats
|
|
99
|
+
const regex = /--([a-zA-Z0-9-]+)\s*:\s*([^;]+);/g;
|
|
100
|
+
|
|
101
|
+
let match;
|
|
102
|
+
while ((match = regex.exec(cssContent)) !== null) {
|
|
103
|
+
const name = match[1].trim();
|
|
104
|
+
const value = match[2].trim();
|
|
105
|
+
variables[`--${name}`] = value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return variables;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Parse Tailwind config file and extract custom color definitions
|
|
113
|
+
* Handles both JS and TS config formats
|
|
114
|
+
*/
|
|
115
|
+
export function parseTailwindConfig(configPath: string, projectRoot: string): Record<string, string> {
|
|
116
|
+
const colors: Record<string, string> = {};
|
|
117
|
+
|
|
118
|
+
const fullPath = path.join(projectRoot, configPath);
|
|
119
|
+
if (!fs.existsSync(fullPath)) {
|
|
120
|
+
return colors;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
125
|
+
|
|
126
|
+
// Extract color definitions from the config
|
|
127
|
+
// This is a simplified parser that handles common patterns
|
|
128
|
+
|
|
129
|
+
// Pattern 1: colors: { name: "value" } or colors: { name: { DEFAULT: "value" } }
|
|
130
|
+
const colorBlockMatch = content.match(/colors\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
131
|
+
if (colorBlockMatch) {
|
|
132
|
+
const colorBlock = colorBlockMatch[1];
|
|
133
|
+
|
|
134
|
+
// Simple key-value pairs: name: "value" or name: 'value'
|
|
135
|
+
const simpleColorRegex = /(\w+[-\w]*)\s*:\s*["']([^"']+)["']/g;
|
|
136
|
+
let match;
|
|
137
|
+
while ((match = simpleColorRegex.exec(colorBlock)) !== null) {
|
|
138
|
+
colors[match[1]] = match[2];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Nested objects with DEFAULT: name: { DEFAULT: "value" }
|
|
142
|
+
const nestedColorRegex = /(\w+[-\w]*)\s*:\s*\{\s*DEFAULT\s*:\s*["']([^"']+)["']/g;
|
|
143
|
+
while ((match = nestedColorRegex.exec(colorBlock)) !== null) {
|
|
144
|
+
colors[match[1]] = match[2];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Pattern 2: extend: { colors: { ... } }
|
|
149
|
+
const extendColorMatch = content.match(/extend\s*:\s*\{[^}]*colors\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
150
|
+
if (extendColorMatch) {
|
|
151
|
+
const extendBlock = extendColorMatch[1];
|
|
152
|
+
|
|
153
|
+
const simpleColorRegex = /(\w+[-\w]*)\s*:\s*["']([^"']+)["']/g;
|
|
154
|
+
let match;
|
|
155
|
+
while ((match = simpleColorRegex.exec(extendBlock)) !== null) {
|
|
156
|
+
colors[match[1]] = match[2];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// CSS variable references: name: "var(--name)" or "hsl(var(--name))"
|
|
160
|
+
const varRefRegex = /(\w+[-\w]*)\s*:\s*["'](?:hsl\()?var\(--([^)]+)\)(?:\))?["']/g;
|
|
161
|
+
while ((match = varRefRegex.exec(extendBlock)) !== null) {
|
|
162
|
+
colors[match[1]] = `var(--${match[2]})`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Silently fail - config parsing is best-effort
|
|
168
|
+
console.warn(`[Theme Discovery] Failed to parse Tailwind config: ${error}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return colors;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Extract available semantic tokens from CSS content
|
|
176
|
+
* Finds class-like patterns that suggest available Tailwind/utility classes
|
|
177
|
+
*/
|
|
178
|
+
export function extractAvailableTokens(cssVariables: Record<string, string>): string[] {
|
|
179
|
+
const tokens: string[] = [];
|
|
180
|
+
|
|
181
|
+
// Generate bg-* and text-* tokens from CSS variable names
|
|
182
|
+
for (const varName of Object.keys(cssVariables)) {
|
|
183
|
+
// Remove -- prefix
|
|
184
|
+
const name = varName.replace(/^--/, "");
|
|
185
|
+
|
|
186
|
+
// Skip numeric or utility variables
|
|
187
|
+
if (/^\d/.test(name) || name.includes("radius") || name.includes("shadow")) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Common color-related variable patterns
|
|
192
|
+
if (
|
|
193
|
+
name.includes("background") ||
|
|
194
|
+
name.includes("foreground") ||
|
|
195
|
+
name.includes("primary") ||
|
|
196
|
+
name.includes("secondary") ||
|
|
197
|
+
name.includes("accent") ||
|
|
198
|
+
name.includes("muted") ||
|
|
199
|
+
name.includes("destructive") ||
|
|
200
|
+
name.includes("success") ||
|
|
201
|
+
name.includes("warning") ||
|
|
202
|
+
name.includes("border") ||
|
|
203
|
+
name.includes("ring") ||
|
|
204
|
+
name.includes("card") ||
|
|
205
|
+
name.includes("popover")
|
|
206
|
+
) {
|
|
207
|
+
// Add as potential class token
|
|
208
|
+
tokens.push(name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Always include common utility tokens
|
|
213
|
+
const commonTokens = [
|
|
214
|
+
"text-white",
|
|
215
|
+
"text-black",
|
|
216
|
+
"bg-white",
|
|
217
|
+
"bg-black",
|
|
218
|
+
"bg-transparent",
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
return [...new Set([...tokens, ...commonTokens])];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Analyze CSS variables to identify contrast relationships
|
|
226
|
+
* Returns warnings about potentially problematic color combinations
|
|
227
|
+
*/
|
|
228
|
+
export function analyzeContrastRelationships(
|
|
229
|
+
cssVariables: Record<string, string>
|
|
230
|
+
): { warnings: string[]; goodPairs: { bg: string; text: string }[] } {
|
|
231
|
+
const warnings: string[] = [];
|
|
232
|
+
const goodPairs: { bg: string; text: string }[] = [];
|
|
233
|
+
|
|
234
|
+
// Common pattern: --{name} is background, --{name}-foreground is text
|
|
235
|
+
// Check if foreground values look like they might have contrast issues
|
|
236
|
+
|
|
237
|
+
for (const [varName, value] of Object.entries(cssVariables)) {
|
|
238
|
+
// Skip if this is a foreground variable
|
|
239
|
+
if (varName.includes("foreground")) continue;
|
|
240
|
+
|
|
241
|
+
const baseName = varName.replace(/^--/, "");
|
|
242
|
+
const foregroundVar = `--${baseName}-foreground`;
|
|
243
|
+
|
|
244
|
+
if (cssVariables[foregroundVar]) {
|
|
245
|
+
const bgValue = value;
|
|
246
|
+
const fgValue = cssVariables[foregroundVar];
|
|
247
|
+
|
|
248
|
+
// Simple heuristic: if both values reference same color or are similar
|
|
249
|
+
// This is a basic check - real contrast would need color parsing
|
|
250
|
+
if (bgValue === fgValue) {
|
|
251
|
+
warnings.push(
|
|
252
|
+
`WARNING: ${varName} and ${foregroundVar} have identical values - text will be invisible`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Add as a known pair (user should verify contrast)
|
|
257
|
+
goodPairs.push({
|
|
258
|
+
bg: `bg-${baseName}`,
|
|
259
|
+
text: `text-${baseName}-foreground`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { warnings, goodPairs };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Main entry point: Discover theme from a project
|
|
269
|
+
*/
|
|
270
|
+
export async function discoverTheme(projectRoot: string): Promise<DiscoveredTheme> {
|
|
271
|
+
const result: DiscoveredTheme = {
|
|
272
|
+
cssVariables: {},
|
|
273
|
+
tailwindColors: {},
|
|
274
|
+
availableTokens: [],
|
|
275
|
+
rawThemeCSS: "",
|
|
276
|
+
discoveredFiles: [],
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Find theme files
|
|
280
|
+
const themeFiles = findThemeFiles(projectRoot);
|
|
281
|
+
result.discoveredFiles = themeFiles;
|
|
282
|
+
|
|
283
|
+
// Parse each file
|
|
284
|
+
for (const file of themeFiles) {
|
|
285
|
+
const fullPath = path.join(projectRoot, file);
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
if (file.endsWith(".css")) {
|
|
289
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
290
|
+
|
|
291
|
+
// Accumulate raw CSS
|
|
292
|
+
result.rawThemeCSS += `\n/* === ${file} === */\n${content}\n`;
|
|
293
|
+
|
|
294
|
+
// Parse variables
|
|
295
|
+
const variables = parseCSSVariables(content);
|
|
296
|
+
Object.assign(result.cssVariables, variables);
|
|
297
|
+
|
|
298
|
+
} else if (file.includes("tailwind.config")) {
|
|
299
|
+
// Parse Tailwind config
|
|
300
|
+
const colors = parseTailwindConfig(file, projectRoot);
|
|
301
|
+
Object.assign(result.tailwindColors, colors);
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.warn(`[Theme Discovery] Error reading ${file}:`, error);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Extract available tokens
|
|
309
|
+
result.availableTokens = extractAvailableTokens(result.cssVariables);
|
|
310
|
+
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Format discovered theme as context for the LLM prompt
|
|
316
|
+
*/
|
|
317
|
+
export function formatThemeForPrompt(theme: DiscoveredTheme): string {
|
|
318
|
+
const lines: string[] = [];
|
|
319
|
+
|
|
320
|
+
lines.push("**TARGET CODEBASE THEME (discovered):**");
|
|
321
|
+
lines.push("");
|
|
322
|
+
|
|
323
|
+
// CSS Variables
|
|
324
|
+
if (Object.keys(theme.cssVariables).length > 0) {
|
|
325
|
+
lines.push("CSS Variables:");
|
|
326
|
+
|
|
327
|
+
// Group by category
|
|
328
|
+
const categories: Record<string, string[]> = {
|
|
329
|
+
colors: [],
|
|
330
|
+
other: [],
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
for (const [name, value] of Object.entries(theme.cssVariables)) {
|
|
334
|
+
const entry = ` ${name}: ${value}`;
|
|
335
|
+
if (
|
|
336
|
+
name.includes("background") ||
|
|
337
|
+
name.includes("foreground") ||
|
|
338
|
+
name.includes("primary") ||
|
|
339
|
+
name.includes("secondary") ||
|
|
340
|
+
name.includes("accent") ||
|
|
341
|
+
name.includes("muted") ||
|
|
342
|
+
name.includes("destructive") ||
|
|
343
|
+
name.includes("success") ||
|
|
344
|
+
name.includes("warning") ||
|
|
345
|
+
name.includes("border") ||
|
|
346
|
+
name.includes("card")
|
|
347
|
+
) {
|
|
348
|
+
categories.colors.push(entry);
|
|
349
|
+
} else {
|
|
350
|
+
categories.other.push(entry);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Only include color-related variables (most relevant for styling)
|
|
355
|
+
lines.push(...categories.colors.slice(0, 30)); // Limit to avoid prompt bloat
|
|
356
|
+
|
|
357
|
+
if (categories.colors.length > 30) {
|
|
358
|
+
lines.push(` ... and ${categories.colors.length - 30} more color variables`);
|
|
359
|
+
}
|
|
360
|
+
lines.push("");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Tailwind Colors
|
|
364
|
+
if (Object.keys(theme.tailwindColors).length > 0) {
|
|
365
|
+
lines.push("Tailwind Custom Colors:");
|
|
366
|
+
for (const [name, value] of Object.entries(theme.tailwindColors)) {
|
|
367
|
+
lines.push(` ${name}: ${value}`);
|
|
368
|
+
}
|
|
369
|
+
lines.push("");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Available Tokens
|
|
373
|
+
if (theme.availableTokens.length > 0) {
|
|
374
|
+
lines.push(`Available Semantic Tokens: ${theme.availableTokens.slice(0, 20).join(", ")}`);
|
|
375
|
+
if (theme.availableTokens.length > 20) {
|
|
376
|
+
lines.push(` ... and ${theme.availableTokens.length - 20} more`);
|
|
377
|
+
}
|
|
378
|
+
lines.push("");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Discovered Files
|
|
382
|
+
if (theme.discoveredFiles.length > 0) {
|
|
383
|
+
lines.push(`Theme Files Found: ${theme.discoveredFiles.join(", ")}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return lines.join("\n");
|
|
387
|
+
}
|
|
388
|
+
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import Anthropic from "@anthropic-ai/sdk";
|
|
5
|
+
import { discoverTheme, formatThemeForPrompt } from "../sonance-vision-apply/theme-discovery";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Sonance DevTools API - Vision Mode Editor
|
|
@@ -469,10 +470,47 @@ Return search/replace patches (NOT full files). The system applies your patches
|
|
|
469
470
|
- CRITICAL: NEVER invent or guess code. Your "search" string MUST be copied EXACTLY from the provided file content. If you cannot find the exact code to modify, return an empty modifications array.
|
|
470
471
|
- If the file content appears truncated, only modify code that is visible in the provided content.
|
|
471
472
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
473
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
474
|
+
SONANCE BRAND COLOR SYSTEM
|
|
475
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
476
|
+
|
|
477
|
+
**Core Colors:**
|
|
478
|
+
- Primary (Charcoal): #333F48 - main text, dark backgrounds
|
|
479
|
+
- Accent (Cyan "The Beam"): #00D3C8 - highlights, interactive elements, CTAs
|
|
480
|
+
- Success: Green tones - positive states, confirmations
|
|
481
|
+
- Warning: Amber/Orange tones - caution states, alerts
|
|
482
|
+
- Destructive: Red tones - errors, delete actions
|
|
483
|
+
|
|
484
|
+
**CRITICAL CONTRAST RULES:**
|
|
485
|
+
When using colored backgrounds, ALWAYS use high-contrast text:
|
|
486
|
+
|
|
487
|
+
| Background | Use This Text | NEVER Use |
|
|
488
|
+
|-------------------|------------------------------------|-----------------------|
|
|
489
|
+
| bg-accent | text-white | text-accent-foreground |
|
|
490
|
+
| bg-primary | text-primary-foreground, text-white | text-primary |
|
|
491
|
+
| bg-success | text-white | text-success |
|
|
492
|
+
| bg-warning | text-white | text-warning |
|
|
493
|
+
| bg-destructive | text-white | text-destructive |
|
|
494
|
+
|
|
495
|
+
**SEMANTIC TOKEN PATTERNS:**
|
|
496
|
+
- text-{color} = the color itself (use on NEUTRAL backgrounds like white/gray)
|
|
497
|
+
- text-{color}-foreground = INTENDED for text on {color} backgrounds, but MAY have contrast issues
|
|
498
|
+
- bg-{color} = background in that color
|
|
499
|
+
- WHEN IN DOUBT: Use text-white on any colored background for guaranteed contrast
|
|
500
|
+
|
|
501
|
+
**BUTTON PATTERNS (Sonance Standard):**
|
|
502
|
+
- Primary CTA: bg-primary text-primary-foreground
|
|
503
|
+
- Accent/Highlight: bg-accent text-white (NOT text-accent-foreground)
|
|
504
|
+
- Success: bg-success text-white
|
|
505
|
+
- Warning: bg-warning text-white
|
|
506
|
+
- Destructive: bg-destructive text-white
|
|
507
|
+
- Outlined: border-border bg-transparent text-foreground
|
|
508
|
+
|
|
509
|
+
**COMMON MISTAKES TO AVOID:**
|
|
510
|
+
- WRONG: bg-accent text-accent-foreground (accent-foreground is often dark = invisible text)
|
|
511
|
+
- RIGHT: bg-accent text-white (white text on cyan = visible)
|
|
512
|
+
- WRONG: bg-primary text-primary (same color = invisible)
|
|
513
|
+
- RIGHT: bg-primary text-primary-foreground OR text-white
|
|
476
514
|
|
|
477
515
|
**RESPONSE FORMAT:**
|
|
478
516
|
CRITICAL: Return ONLY the JSON object below. Do NOT include any text, explanation, or thinking before or after the JSON. No preamble. No "Looking at the screenshot..." No markdown code blocks. Just raw JSON:
|
|
@@ -675,7 +713,7 @@ export async function POST(request: Request) {
|
|
|
675
713
|
const TOTAL_CONTEXT_BUDGET = 500000; // 500k chars total budget
|
|
676
714
|
const MAX_RECOMMENDED_FILE = Infinity; // NEVER truncate the target file - AI needs full context
|
|
677
715
|
const MAX_PAGE_FILE = 2000; // Page file is just a wrapper
|
|
678
|
-
const MAX_GLOBALS_CSS =
|
|
716
|
+
const MAX_GLOBALS_CSS = 15000; // Increased to capture full theme definitions
|
|
679
717
|
const MAX_FILES = 25;
|
|
680
718
|
|
|
681
719
|
let usedContext = 0;
|
|
@@ -769,6 +807,25 @@ ${truncatedContent}${wasTruncated ? "\n// ... (truncated)" : ""}
|
|
|
769
807
|
}
|
|
770
808
|
}
|
|
771
809
|
|
|
810
|
+
// ========== THEME DISCOVERY ==========
|
|
811
|
+
// Dynamically discover theme tokens from the target codebase
|
|
812
|
+
const discoveredTheme = await discoverTheme(projectRoot);
|
|
813
|
+
const themeContext = formatThemeForPrompt(discoveredTheme);
|
|
814
|
+
|
|
815
|
+
if (discoveredTheme.discoveredFiles.length > 0) {
|
|
816
|
+
textContent += `
|
|
817
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
818
|
+
${themeContext}
|
|
819
|
+
═══════════════════════════════════════════════════════════════════════════════
|
|
820
|
+
|
|
821
|
+
`;
|
|
822
|
+
debugLog("Theme discovery complete", {
|
|
823
|
+
filesFound: discoveredTheme.discoveredFiles,
|
|
824
|
+
cssVariableCount: Object.keys(discoveredTheme.cssVariables).length,
|
|
825
|
+
tailwindColorCount: Object.keys(discoveredTheme.tailwindColors).length,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
772
829
|
// ========== GLOBALS CSS ==========
|
|
773
830
|
const globalsTruncated = pageContext.globalsCSS.substring(0, MAX_GLOBALS_CSS);
|
|
774
831
|
textContent += `
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.53",
|
|
4
4
|
"description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|