sonance-brand-mcp 1.3.48 → 1.3.50
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-vision-apply/route.ts +45 -20
- package/dist/assets/api/sonance-vision-edit/route.ts +44 -20
- package/dist/assets/dev-tools/SonanceDevTools.tsx +18 -0
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +63 -31
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +3 -0
- package/package.json +1 -1
|
@@ -545,6 +545,7 @@ export async function POST(request: Request) {
|
|
|
545
545
|
return NextResponse.json({
|
|
546
546
|
success: result.success,
|
|
547
547
|
message: result.message,
|
|
548
|
+
error: result.success ? undefined : result.message, // Include error for client compatibility
|
|
548
549
|
filesReverted: result.filesReverted,
|
|
549
550
|
});
|
|
550
551
|
}
|
|
@@ -846,6 +847,14 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
846
847
|
// If this is a retry, add feedback about what went wrong
|
|
847
848
|
if (retryCount > 0 && lastPatchErrors.length > 0) {
|
|
848
849
|
debugLog("Retry attempt with feedback", { retryCount, errorCount: lastPatchErrors.length });
|
|
850
|
+
|
|
851
|
+
// Get a snippet of the actual file content to help AI find correct code
|
|
852
|
+
let fileSnippet = "";
|
|
853
|
+
if (recommendedFileContent) {
|
|
854
|
+
// Show first 800 chars to give AI context about what code actually exists
|
|
855
|
+
fileSnippet = recommendedFileContent.content.substring(0, 800).replace(/\n/g, "\n");
|
|
856
|
+
}
|
|
857
|
+
|
|
849
858
|
currentMessages.push({
|
|
850
859
|
role: "assistant",
|
|
851
860
|
content: "I'll analyze the screenshot and generate the patches now.",
|
|
@@ -857,10 +866,20 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
857
866
|
Failed patches:
|
|
858
867
|
${lastPatchErrors.join("\n\n")}
|
|
859
868
|
|
|
860
|
-
|
|
861
|
-
|
|
869
|
+
${fileSnippet ? `Here is the ACTUAL beginning of the file you're trying to edit:
|
|
870
|
+
\`\`\`tsx
|
|
871
|
+
${fileSnippet}...
|
|
872
|
+
\`\`\`
|
|
873
|
+
` : ""}
|
|
874
|
+
CRITICAL INSTRUCTIONS:
|
|
875
|
+
1. Your "search" string MUST be copied EXACTLY from the file content I provided above
|
|
876
|
+
2. Do NOT invent, guess, or imagine code that might exist
|
|
877
|
+
3. Look for the ACTUAL code in the TARGET COMPONENT section and copy it exactly
|
|
862
878
|
|
|
863
|
-
If you cannot find the exact code to modify
|
|
879
|
+
If you still cannot find the exact code to modify after reviewing the file:
|
|
880
|
+
- Return {"modifications": [], "explanation": "Could not locate the exact code. Please specify which element you want to change more precisely."}
|
|
881
|
+
|
|
882
|
+
This is better than generating patches with made-up code.`,
|
|
864
883
|
});
|
|
865
884
|
}
|
|
866
885
|
|
|
@@ -1849,6 +1868,7 @@ function searchFilesForKeywords(
|
|
|
1849
1868
|
let cachedPathAliases: Map<string, string> | null = null;
|
|
1850
1869
|
let cachedProjectRoot: string | null = null;
|
|
1851
1870
|
let cachedTsconfigMtime: number | null = null;
|
|
1871
|
+
let cachedParseError: string | null = null; // Cache parse errors to avoid log spam
|
|
1852
1872
|
|
|
1853
1873
|
/**
|
|
1854
1874
|
* Clean tsconfig.json content to make it valid JSON
|
|
@@ -1921,21 +1941,23 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1921
1941
|
cachedTsconfigMtime = null;
|
|
1922
1942
|
}
|
|
1923
1943
|
} catch (e) {
|
|
1924
|
-
// Log the error with more context for debugging
|
|
1925
1944
|
const errorStr = String(e);
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
if (
|
|
1929
|
-
const
|
|
1930
|
-
|
|
1931
|
-
|
|
1945
|
+
|
|
1946
|
+
// Only log parse error once to avoid log spam
|
|
1947
|
+
if (!cachedParseError) {
|
|
1948
|
+
const posMatch = errorStr.match(/position (\d+)/);
|
|
1949
|
+
let context = "";
|
|
1950
|
+
if (posMatch) {
|
|
1951
|
+
const pos = parseInt(posMatch[1], 10);
|
|
1952
|
+
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
1953
|
+
context = `Near: "${content.substring(Math.max(0, pos - 20), pos + 20)}"`;
|
|
1954
|
+
}
|
|
1955
|
+
debugLog("[apply] Failed to parse tsconfig.json (will use defaults, logging once)", { error: errorStr, context });
|
|
1956
|
+
cachedParseError = errorStr;
|
|
1932
1957
|
}
|
|
1933
|
-
debugLog("[apply] Failed to parse tsconfig.json", { error: errorStr, context });
|
|
1934
1958
|
|
|
1935
|
-
//
|
|
1936
|
-
cachedPathAliases
|
|
1937
|
-
cachedProjectRoot = null;
|
|
1938
|
-
cachedTsconfigMtime = null;
|
|
1959
|
+
// Don't clear cache - we'll use the fallback aliases
|
|
1960
|
+
// cachedPathAliases will remain null, triggering fallback below
|
|
1939
1961
|
}
|
|
1940
1962
|
}
|
|
1941
1963
|
|
|
@@ -1950,13 +1972,16 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1950
1972
|
} else {
|
|
1951
1973
|
aliases.set("@/", "");
|
|
1952
1974
|
}
|
|
1953
|
-
|
|
1975
|
+
// Only log default alias once (not when using cached error fallback)
|
|
1976
|
+
if (!cachedParseError) {
|
|
1977
|
+
debugLog("[apply] Using default @/ alias", { alias: aliases.get("@/") });
|
|
1978
|
+
}
|
|
1954
1979
|
}
|
|
1955
1980
|
|
|
1956
|
-
//
|
|
1957
|
-
if (parsedSuccessfully || !fs.existsSync(tsconfigPath)) {
|
|
1958
|
-
|
|
1959
|
-
|
|
1981
|
+
// Cache aliases if parsed successfully, no tsconfig exists, OR we have a parse error (cache fallback)
|
|
1982
|
+
if (parsedSuccessfully || !fs.existsSync(tsconfigPath) || cachedParseError) {
|
|
1983
|
+
cachedPathAliases = aliases;
|
|
1984
|
+
cachedProjectRoot = projectRoot;
|
|
1960
1985
|
}
|
|
1961
1986
|
|
|
1962
1987
|
return aliases;
|
|
@@ -852,6 +852,14 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
852
852
|
// If this is a retry, add feedback about what went wrong
|
|
853
853
|
if (retryCount > 0 && lastPatchErrors.length > 0) {
|
|
854
854
|
debugLog("Retry attempt with feedback", { retryCount, errorCount: lastPatchErrors.length });
|
|
855
|
+
|
|
856
|
+
// Get a snippet of the actual file content to help AI find correct code
|
|
857
|
+
let fileSnippet = "";
|
|
858
|
+
if (recommendedFileContent) {
|
|
859
|
+
// Show first 800 chars to give AI context about what code actually exists
|
|
860
|
+
fileSnippet = recommendedFileContent.content.substring(0, 800).replace(/\n/g, "\n");
|
|
861
|
+
}
|
|
862
|
+
|
|
855
863
|
currentMessages.push({
|
|
856
864
|
role: "assistant",
|
|
857
865
|
content: "I'll analyze the screenshot and generate the patches now.",
|
|
@@ -863,10 +871,20 @@ CRITICAL: Edit the TARGET COMPONENT (marked with ***), not the page wrapper.`;
|
|
|
863
871
|
Failed patches:
|
|
864
872
|
${lastPatchErrors.join("\n\n")}
|
|
865
873
|
|
|
866
|
-
|
|
867
|
-
|
|
874
|
+
${fileSnippet ? `Here is the ACTUAL beginning of the file you're trying to edit:
|
|
875
|
+
\`\`\`tsx
|
|
876
|
+
${fileSnippet}...
|
|
877
|
+
\`\`\`
|
|
878
|
+
` : ""}
|
|
879
|
+
CRITICAL INSTRUCTIONS:
|
|
880
|
+
1. Your "search" string MUST be copied EXACTLY from the file content I provided above
|
|
881
|
+
2. Do NOT invent, guess, or imagine code that might exist
|
|
882
|
+
3. Look for the ACTUAL code in the TARGET COMPONENT section and copy it exactly
|
|
868
883
|
|
|
869
|
-
If you cannot find the exact code to modify
|
|
884
|
+
If you still cannot find the exact code to modify after reviewing the file:
|
|
885
|
+
- Return {"modifications": [], "explanation": "Could not locate the exact code. Please specify which element you want to change more precisely."}
|
|
886
|
+
|
|
887
|
+
This is better than generating patches with made-up code.`,
|
|
870
888
|
});
|
|
871
889
|
}
|
|
872
890
|
|
|
@@ -1755,6 +1773,7 @@ function searchFilesForKeywords(
|
|
|
1755
1773
|
let cachedPathAliases: Map<string, string> | null = null;
|
|
1756
1774
|
let cachedProjectRoot: string | null = null;
|
|
1757
1775
|
let cachedTsconfigMtime: number | null = null;
|
|
1776
|
+
let cachedParseError: string | null = null; // Cache parse errors to avoid log spam
|
|
1758
1777
|
|
|
1759
1778
|
/**
|
|
1760
1779
|
* Clean tsconfig.json content to make it valid JSON
|
|
@@ -1827,21 +1846,23 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1827
1846
|
cachedTsconfigMtime = null;
|
|
1828
1847
|
}
|
|
1829
1848
|
} catch (e) {
|
|
1830
|
-
// Log the error with more context for debugging
|
|
1831
1849
|
const errorStr = String(e);
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
if (
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
|
|
1850
|
+
|
|
1851
|
+
// Only log parse error once to avoid log spam
|
|
1852
|
+
if (!cachedParseError) {
|
|
1853
|
+
const posMatch = errorStr.match(/position (\d+)/);
|
|
1854
|
+
let context = "";
|
|
1855
|
+
if (posMatch) {
|
|
1856
|
+
const pos = parseInt(posMatch[1], 10);
|
|
1857
|
+
const content = fs.readFileSync(tsconfigPath, "utf-8");
|
|
1858
|
+
context = `Near: "${content.substring(Math.max(0, pos - 20), pos + 20)}"`;
|
|
1859
|
+
}
|
|
1860
|
+
debugLog("[edit] Failed to parse tsconfig.json (will use defaults, logging once)", { error: errorStr, context });
|
|
1861
|
+
cachedParseError = errorStr;
|
|
1838
1862
|
}
|
|
1839
|
-
debugLog("[edit] Failed to parse tsconfig.json", { error: errorStr, context });
|
|
1840
1863
|
|
|
1841
|
-
//
|
|
1842
|
-
cachedPathAliases
|
|
1843
|
-
cachedProjectRoot = null;
|
|
1844
|
-
cachedTsconfigMtime = null;
|
|
1864
|
+
// Don't clear cache - we'll use the fallback aliases
|
|
1865
|
+
// cachedPathAliases will remain null, triggering fallback below
|
|
1845
1866
|
}
|
|
1846
1867
|
}
|
|
1847
1868
|
|
|
@@ -1856,13 +1877,16 @@ function getPathAliases(projectRoot: string): Map<string, string> {
|
|
|
1856
1877
|
} else {
|
|
1857
1878
|
aliases.set("@/", "");
|
|
1858
1879
|
}
|
|
1859
|
-
|
|
1880
|
+
// Only log default alias once (not when using cached error fallback)
|
|
1881
|
+
if (!cachedParseError) {
|
|
1882
|
+
debugLog("[edit] Using default @/ alias", { alias: aliases.get("@/") });
|
|
1883
|
+
}
|
|
1860
1884
|
}
|
|
1861
1885
|
|
|
1862
|
-
//
|
|
1863
|
-
if (parsedSuccessfully || !fs.existsSync(tsconfigPath)) {
|
|
1864
|
-
|
|
1865
|
-
|
|
1886
|
+
// Cache aliases if parsed successfully, no tsconfig exists, OR we have a parse error (cache fallback)
|
|
1887
|
+
if (parsedSuccessfully || !fs.existsSync(tsconfigPath) || cachedParseError) {
|
|
1888
|
+
cachedPathAliases = aliases;
|
|
1889
|
+
cachedProjectRoot = projectRoot;
|
|
1866
1890
|
}
|
|
1867
1891
|
|
|
1868
1892
|
return aliases;
|
|
@@ -1228,6 +1228,23 @@ export function SonanceDevTools() {
|
|
|
1228
1228
|
}
|
|
1229
1229
|
}, [applyFirstSession]);
|
|
1230
1230
|
|
|
1231
|
+
// Force clear stale session - clears state without calling API
|
|
1232
|
+
const handleApplyFirstForceClear = useCallback(() => {
|
|
1233
|
+
console.log("[Apply-First] Force clearing stale session");
|
|
1234
|
+
setApplyFirstSession(null);
|
|
1235
|
+
setApplyFirstStatus("idle");
|
|
1236
|
+
setVisionFocusedElements([]);
|
|
1237
|
+
setChangedElements([]);
|
|
1238
|
+
|
|
1239
|
+
// Clear persisted session from localStorage
|
|
1240
|
+
try {
|
|
1241
|
+
localStorage.removeItem("sonance-apply-first-session");
|
|
1242
|
+
console.log("[Apply-First] Session force-cleared from localStorage");
|
|
1243
|
+
} catch (e) {
|
|
1244
|
+
console.warn("[Apply-First] Failed to clear session:", e);
|
|
1245
|
+
}
|
|
1246
|
+
}, []);
|
|
1247
|
+
|
|
1231
1248
|
// Auto-revert on navigation (beforeunload)
|
|
1232
1249
|
useEffect(() => {
|
|
1233
1250
|
if (!applyFirstSession) return;
|
|
@@ -2533,6 +2550,7 @@ export function SonanceDevTools() {
|
|
|
2533
2550
|
onApplyFirstComplete={handleApplyFirstComplete}
|
|
2534
2551
|
onApplyFirstAccept={handleApplyFirstAccept}
|
|
2535
2552
|
onApplyFirstRevert={handleApplyFirstRevert}
|
|
2553
|
+
onApplyFirstForceClear={handleApplyFirstForceClear}
|
|
2536
2554
|
/>
|
|
2537
2555
|
)}
|
|
2538
2556
|
|
|
@@ -10,6 +10,7 @@ export interface ApplyFirstPreviewProps {
|
|
|
10
10
|
status: ApplyFirstStatus;
|
|
11
11
|
onAccept: () => void;
|
|
12
12
|
onRevert: () => void;
|
|
13
|
+
onForceClear?: () => void; // Force clear stale sessions
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function FileModificationCard({
|
|
@@ -100,6 +101,7 @@ export function ApplyFirstPreview({
|
|
|
100
101
|
status,
|
|
101
102
|
onAccept,
|
|
102
103
|
onRevert,
|
|
104
|
+
onForceClear,
|
|
103
105
|
}: ApplyFirstPreviewProps) {
|
|
104
106
|
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
|
|
105
107
|
|
|
@@ -124,6 +126,7 @@ export function ApplyFirstPreview({
|
|
|
124
126
|
|
|
125
127
|
const fileCount = session.modifications.length;
|
|
126
128
|
const isLoading = status === "accepting" || status === "reverting";
|
|
129
|
+
const isStaleSession = fileCount === 0 || status === "error";
|
|
127
130
|
|
|
128
131
|
return (
|
|
129
132
|
<div className="space-y-3 p-3 rounded border border-green-300 bg-green-50">
|
|
@@ -141,14 +144,24 @@ export function ApplyFirstPreview({
|
|
|
141
144
|
<HMRStatusBadge status={status} />
|
|
142
145
|
</div>
|
|
143
146
|
|
|
144
|
-
{/* Info Banner */}
|
|
145
|
-
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
{/* Info Banner - show different message for stale sessions */}
|
|
148
|
+
{isStaleSession ? (
|
|
149
|
+
<div className="flex items-start gap-2 p-2 rounded bg-amber-50 border border-amber-200">
|
|
150
|
+
<AlertTriangle className="h-3.5 w-3.5 text-amber-600 mt-0.5 flex-shrink-0" />
|
|
151
|
+
<span className="text-xs text-amber-700">
|
|
152
|
+
<strong>Stale session detected.</strong> The backup files may no longer exist.
|
|
153
|
+
Use "Force Clear" to dismiss this panel.
|
|
154
|
+
</span>
|
|
155
|
+
</div>
|
|
156
|
+
) : (
|
|
157
|
+
<div className="flex items-start gap-2 p-2 rounded bg-blue-50 border border-blue-200">
|
|
158
|
+
<Info className="h-3.5 w-3.5 text-blue-600 mt-0.5 flex-shrink-0" />
|
|
159
|
+
<span className="text-xs text-blue-700">
|
|
160
|
+
<strong>Changes are live!</strong> Scroll around to see the actual result.
|
|
161
|
+
Your original files are safely backed up.
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
152
165
|
|
|
153
166
|
{/* File Modifications List */}
|
|
154
167
|
<div className="space-y-2 max-h-80 overflow-y-auto">
|
|
@@ -172,29 +185,31 @@ export function ApplyFirstPreview({
|
|
|
172
185
|
|
|
173
186
|
{/* Action Buttons */}
|
|
174
187
|
<div className="flex gap-2">
|
|
175
|
-
{/* Accept Button */}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
{/* Accept Button - hide for stale sessions */}
|
|
189
|
+
{!isStaleSession && (
|
|
190
|
+
<button
|
|
191
|
+
onClick={onAccept}
|
|
192
|
+
disabled={isLoading}
|
|
193
|
+
className={cn(
|
|
194
|
+
"flex-1 flex items-center justify-center gap-2 py-2.5",
|
|
195
|
+
"text-xs font-medium rounded transition-colors",
|
|
196
|
+
"bg-green-600 text-white hover:bg-green-700",
|
|
197
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
198
|
+
)}
|
|
199
|
+
>
|
|
200
|
+
{status === "accepting" ? (
|
|
201
|
+
<>
|
|
202
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
203
|
+
Accepting...
|
|
204
|
+
</>
|
|
205
|
+
) : (
|
|
206
|
+
<>
|
|
207
|
+
<Check className="h-3.5 w-3.5" />
|
|
208
|
+
Keep Changes
|
|
209
|
+
</>
|
|
210
|
+
)}
|
|
211
|
+
</button>
|
|
212
|
+
)}
|
|
198
213
|
|
|
199
214
|
{/* Revert Button */}
|
|
200
215
|
<button
|
|
@@ -219,6 +234,23 @@ export function ApplyFirstPreview({
|
|
|
219
234
|
</>
|
|
220
235
|
)}
|
|
221
236
|
</button>
|
|
237
|
+
|
|
238
|
+
{/* Force Clear Button - show for stale sessions or errors */}
|
|
239
|
+
{isStaleSession && onForceClear && (
|
|
240
|
+
<button
|
|
241
|
+
onClick={onForceClear}
|
|
242
|
+
disabled={isLoading}
|
|
243
|
+
className={cn(
|
|
244
|
+
"flex-1 flex items-center justify-center gap-2 py-2.5",
|
|
245
|
+
"text-xs font-medium rounded transition-colors",
|
|
246
|
+
"border border-red-300 text-red-700 bg-red-50 hover:bg-red-100",
|
|
247
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
248
|
+
)}
|
|
249
|
+
>
|
|
250
|
+
<X className="h-3.5 w-3.5" />
|
|
251
|
+
Force Clear
|
|
252
|
+
</button>
|
|
253
|
+
)}
|
|
222
254
|
</div>
|
|
223
255
|
|
|
224
256
|
{/* Session Info (for debugging) */}
|
|
@@ -56,6 +56,7 @@ export interface ComponentsPanelProps {
|
|
|
56
56
|
onApplyFirstComplete?: (session: ApplyFirstSession) => void;
|
|
57
57
|
onApplyFirstAccept?: () => void;
|
|
58
58
|
onApplyFirstRevert?: () => void;
|
|
59
|
+
onApplyFirstForceClear?: () => void;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export function ComponentsPanel({
|
|
@@ -95,6 +96,7 @@ export function ComponentsPanel({
|
|
|
95
96
|
onApplyFirstComplete,
|
|
96
97
|
onApplyFirstAccept,
|
|
97
98
|
onApplyFirstRevert,
|
|
99
|
+
onApplyFirstForceClear,
|
|
98
100
|
}: ComponentsPanelProps) {
|
|
99
101
|
// Auto-activate inspector when entering this tab
|
|
100
102
|
useEffect(() => {
|
|
@@ -578,6 +580,7 @@ export function ComponentsPanel({
|
|
|
578
580
|
status={applyFirstStatus}
|
|
579
581
|
onAccept={onApplyFirstAccept}
|
|
580
582
|
onRevert={onApplyFirstRevert}
|
|
583
|
+
onForceClear={onApplyFirstForceClear}
|
|
581
584
|
/>
|
|
582
585
|
)}
|
|
583
586
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.50",
|
|
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",
|