dev3000 0.0.80 → 0.0.82
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/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +40 -30
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +12 -12
- package/dist/cli.js.map +1 -1
- package/dist/constants/log-colors.d.ts +20 -23
- package/dist/constants/log-colors.d.ts.map +1 -1
- package/dist/constants/log-colors.js +25 -26
- package/dist/constants/log-colors.js.map +1 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +15 -20
- package/dist/dev-environment.js.map +1 -1
- package/dist/screencast-manager.d.ts +0 -1
- package/dist/screencast-manager.d.ts.map +1 -1
- package/dist/screencast-manager.js +47 -9
- package/dist/screencast-manager.js.map +1 -1
- package/dist/src/tui-interface-impl.tsx +43 -26
- package/dist/tui-interface-impl.d.ts.map +1 -1
- package/dist/tui-interface-impl.js +35 -22
- package/dist/tui-interface-impl.js.map +1 -1
- package/mcp-server/.next/BUILD_ID +1 -1
- package/mcp-server/.next/build-manifest.json +5 -5
- package/mcp-server/.next/fallback-build-manifest.json +2 -2
- package/mcp-server/.next/prerender-manifest.json +3 -3
- package/mcp-server/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/_global-error.html +2 -2
- package/mcp-server/.next/server/app/_global-error.rsc +1 -1
- package/mcp-server/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/_not-found.html +1 -1
- package/mcp-server/.next/server/app/_not-found.rsc +2 -2
- package/mcp-server/.next/server/app/index.html +1 -1
- package/mcp-server/.next/server/app/index.rsc +3 -3
- package/mcp-server/.next/server/app/logs/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/mcp/route.js +2 -2
- package/mcp-server/.next/server/app/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/app/video/[session]/page/build-manifest.json +3 -3
- package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +1 -1
- package/mcp-server/.next/server/app/video/[session]/page_client-reference-manifest.js +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__00592d3f._.js +64 -7
- package/mcp-server/.next/server/chunks/[root-of-the-server]__00592d3f._.js.map +1 -1
- package/mcp-server/.next/server/chunks/[root-of-the-server]__177c72c6._.js +5 -23
- package/mcp-server/.next/server/chunks/[root-of-the-server]__177c72c6._.js.map +1 -1
- package/mcp-server/.next/server/chunks/d1d76_next_dist_esm_build_templates_app-route_820fc951.js +1 -1
- package/mcp-server/.next/server/chunks/d1d76_next_dist_esm_build_templates_app-route_820fc951.js.map +1 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__22d96eb1._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__22d96eb1._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__63dd19ce._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__63dd19ce._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__94d8e211._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__94d8e211._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__0217e89c._.js → [root-of-the-server]__cf17e26a._.js} +2 -2
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__cf17e26a._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_96ba1d8e._.js +8 -0
- package/mcp-server/.next/server/chunks/ssr/_96ba1d8e._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/_aa32a659._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/_aa32a659._.js.map +1 -0
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_b2368b47._.js +3 -0
- package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_b2368b47._.js.map +1 -0
- package/mcp-server/.next/server/middleware-build-manifest.js +3 -3
- package/mcp-server/.next/server/server-reference-manifest.js +1 -1
- package/mcp-server/.next/server/server-reference-manifest.json +1 -1
- package/mcp-server/.next/static/chunks/{3d37ed424c6aaf63.css → 0cf268b98f36b0b5.css} +1 -1
- package/mcp-server/.next/static/chunks/4b6eda65c6f5aada.js +1 -0
- package/mcp-server/.next/static/chunks/55cedb1a993e951c.js +1 -0
- package/mcp-server/.next/static/chunks/5fdb882fe407a798.js +1 -0
- package/mcp-server/.next/static/chunks/7abbb9a17b62c1fa.js +1 -0
- package/mcp-server/.next/static/chunks/98e04adc8a00688a.js +1 -0
- package/mcp-server/.next/static/chunks/db42008ac1e97815.js +1 -0
- package/mcp-server/.next/static/chunks/f08166f7946c2e06.js +1 -0
- package/mcp-server/.next/static/chunks/{turbopack-9656e7304584cab2.js → turbopack-7112e6b6aee1d84f.js} +1 -1
- package/mcp-server/app/api/jank/[session]/route.ts +1 -1
- package/mcp-server/app/logs/LogsClient.tsx +37 -37
- package/mcp-server/app/mcp/route.ts +26 -112
- package/mcp-server/app/mcp/tools.ts +455 -20
- package/mcp-server/app/video/[session]/page.tsx +4 -2
- package/package.json +1 -1
- package/src/tui-interface-impl.tsx +43 -26
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__0217e89c._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__91510608._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__91510608._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e1bc1b8a._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e1bc1b8a._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__fb40854a._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__fb40854a._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/_62451611._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/_62451611._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/_b15f05ee._.js +0 -3
- package/mcp-server/.next/server/chunks/ssr/_b15f05ee._.js.map +0 -1
- package/mcp-server/.next/server/chunks/ssr/_bacf0748._.js +0 -8
- package/mcp-server/.next/server/chunks/ssr/_bacf0748._.js.map +0 -1
- package/mcp-server/.next/static/chunks/274a8d03fad7f819.js +0 -1
- package/mcp-server/.next/static/chunks/543e14c771a22442.js +0 -1
- package/mcp-server/.next/static/chunks/58fdd5192b305065.js +0 -1
- package/mcp-server/.next/static/chunks/6bd684c2018a357c.js +0 -1
- package/mcp-server/.next/static/chunks/6d59e588420330ca.js +0 -1
- package/mcp-server/.next/static/chunks/9625e4da85a132f3.js +0 -1
- package/mcp-server/.next/static/chunks/c36bc797d535a4dc.js +0 -1
- /package/mcp-server/.next/static/{xyH2s6kJYyYw2j2TSlqpE → A1zsECiQync8Ag1Q1mulr}/_buildManifest.js +0 -0
- /package/mcp-server/.next/static/{xyH2s6kJYyYw2j2TSlqpE → A1zsECiQync8Ag1Q1mulr}/_clientMiddlewareManifest.json +0 -0
- /package/mcp-server/.next/static/{xyH2s6kJYyYw2j2TSlqpE → A1zsECiQync8Ag1Q1mulr}/_ssgManifest.js +0 -0
|
@@ -14,17 +14,14 @@ export const TOOL_DESCRIPTIONS = {
|
|
|
14
14
|
fix_my_app:
|
|
15
15
|
"🔧 **THE ULTIMATE FIND→FIX→VERIFY MACHINE!** This tool doesn't just find bugs - it FIXES them! Pure dev3000 magic that identifies issues, provides exact fixes, and verifies everything works! 🪄\n\n🔥 **INSTANT FIXING SUPERPOWERS:**\n• Detects ALL error types: server crashes, browser errors, build failures, API issues, performance problems\n• Shows EXACT user interactions that triggered each error (clicks, navigation, etc.)\n• Provides EXACT fix code with file locations and line numbers\n• Guides you through implementing fixes step-by-step\n• Verifies fixes by replaying the same interactions that caused the error!\n\n📍 **INTERACTION-BASED VERIFICATION:**\n• Every error includes the user interactions that led to it\n• Use execute_browser_action to replay these exact interactions\n• Verify your fix works by confirming the error doesn't reoccur\n• Example: Error shows '[INTERACTION] Click at (450,300)' → After fix, use execute_browser_action(action='click', params={x:450, y:300}) to verify\n\n⚡ **3 ACTION MODES:**\n• FIX NOW: 'What's broken RIGHT NOW?' → Find and fix immediately\n• FIX REGRESSION: 'What broke during testing?' → Compare before/after and fix\n• FIX CONTINUOUSLY: 'Fix issues as they appear' → Monitor and fix proactively\n\n🎪 **THE FIX-IT WORKFLOW:**\n1️⃣ I FIND all issues with their triggering interactions\n2️⃣ I provide EXACT FIXES with code snippets\n3️⃣ You implement the fixes\n4️⃣ We REPLAY the interactions to VERIFY everything works\n\n💡 **PERFECT FOR:** 'fix my app' or 'debug my app' requests, error resolution, code repairs, making broken apps work again. This tool doesn't just identify problems - it SOLVES them with precise reproduction steps!",
|
|
16
16
|
|
|
17
|
-
create_integrated_workflow:
|
|
18
|
-
"🧠 **INTELLIGENT DEBUGGING ORCHESTRATOR** - Transform dev3000 from a standalone tool into the conductor of your debugging orchestra! This tool automatically detects available MCPs and creates integrated workflows that leverage the unique strengths of each tool.\n\n🎼 **ORCHESTRATION SUPERPOWERS:**\n• Auto-detects nextjs-dev and chrome-devtools MCPs when available\n• Creates 3-phase systematic debugging workflows\n• Provides AI-powered correlation between server/client/browser layers\n• Returns concrete function calls for Claude to execute across MCPs\n\n⚡ **3-PHASE WORKFLOW MAGIC:**\n• Phase 1: Parallel Data Collection (across all available MCPs)\n• Phase 2: Deep Targeted Analysis (sequential, context-aware)\n• Phase 3: Fix Implementation & Verification (orchestrated testing)\n\n🔗 **INTEGRATION BENEFITS:**\n• With nextjs-dev: Framework-specific build/runtime error context\n• With chrome-devtools: Precise browser state inspection\n• Together: Complete full-stack debugging coverage with AI correlation\n\n💡 **PERFECT FOR:** Multi-MCP environments where you want dev3000 to intelligently coordinate debugging across tools instead of using them individually. Makes other MCPs more powerful when used together!",
|
|
19
|
-
|
|
20
17
|
execute_browser_action:
|
|
21
18
|
"🌐 **INTELLIGENT BROWSER AUTOMATION** - Smart browser action routing that automatically delegates to chrome-devtools MCP when available for superior automation capabilities.\n\n🎯 **INTELLIGENT DELEGATION:**\n• Screenshots → chrome-devtools MCP (better quality, no conflicts)\n• Navigation → chrome-devtools MCP (more reliable page handling)\n• Clicks → chrome-devtools MCP (precise coordinate-based interaction)\n• JavaScript evaluation → chrome-devtools MCP (enhanced debugging)\n• Scrolling & typing → dev3000 fallback (specialized actions)\n\n⚡ **PROGRESSIVE ENHANCEMENT:**\n• Uses chrome-devtools MCP when available for best results\n• Falls back to dev3000's native implementation when chrome-devtools unavailable\n• Shares the same Chrome instance via CDP URL coordination\n• Eliminates browser conflicts between tools\n\n💡 **PERFECT FOR:** Browser automation that automatically chooses the best tool for each action, ensuring optimal results whether chrome-devtools MCP is available or not.",
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
"🔍 **
|
|
20
|
+
analyze_visual_diff:
|
|
21
|
+
"🔍 **VISUAL DIFF ANALYZER** - Analyzes two screenshots to identify and describe visual differences. Returns detailed instructions for Claude to load and compare the images, focusing on what changed that could cause layout shifts.\n\n🎯 **WHAT IT PROVIDES:**\n• Direct instructions to load both images via Read tool\n• Context about what to look for\n• Guidance on identifying layout shift causes\n• Structured format for easy analysis\n\n💡 **PERFECT FOR:** Understanding what visual changes occurred between before/after frames in CLS detection, identifying elements that appeared/moved/resized.",
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
"🔍 **
|
|
23
|
+
find_component_source:
|
|
24
|
+
"🔍 **COMPONENT SOURCE FINDER** - Maps DOM elements to their source code by extracting the React component function and finding unique patterns to search for.\n\n🎯 **HOW IT WORKS:**\n• Inspects the element via Chrome DevTools Protocol\n• Extracts the React component function source using .toString()\n• Identifies unique code patterns (specific JSX, classNames, imports)\n• Returns targeted grep patterns to find the exact source file\n\n💡 **PERFECT FOR:** Finding which file contains the code for a specific element, especially useful for CLS debugging when you need to fix layout shifts in specific components."
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
// Types
|
|
@@ -645,7 +642,7 @@ export async function fixMyApp({
|
|
|
645
642
|
)
|
|
646
643
|
}
|
|
647
644
|
|
|
648
|
-
results.push(`📹 **
|
|
645
|
+
results.push(`📹 **View all frames**: ${videoUrl}`)
|
|
649
646
|
results.push(`🎞️ **Session ID**: ${jankResult.sessionId} (${jankResult.totalFrames} frames)`)
|
|
650
647
|
results.push("")
|
|
651
648
|
|
|
@@ -660,6 +657,26 @@ export async function fixMyApp({
|
|
|
660
657
|
`${emoji} **${jank.timeSinceStart}ms**: ${jank.visualDiff.toFixed(1)}% of screen changed (${jank.severity} severity)`
|
|
661
658
|
)
|
|
662
659
|
}
|
|
660
|
+
|
|
661
|
+
// Include Before/After frame URLs if available
|
|
662
|
+
if (jank.beforeFrameUrl && jank.afterFrameUrl) {
|
|
663
|
+
results.push(` 📸 Before: ${jank.beforeFrameUrl}`)
|
|
664
|
+
results.push(` 📸 After: ${jank.afterFrameUrl}`)
|
|
665
|
+
results.push(
|
|
666
|
+
` 💡 Use analyze_visual_diff tool with these URLs to get a detailed description of what changed`
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
// Extract CSS selector from element description (e.g., "Navigation header (<nav>)" -> "nav")
|
|
670
|
+
if (jank.element) {
|
|
671
|
+
const selectorMatch = jank.element.match(/<(\w+)>/)
|
|
672
|
+
if (selectorMatch) {
|
|
673
|
+
const selector = selectorMatch[1].toLowerCase()
|
|
674
|
+
results.push(
|
|
675
|
+
` 💡 Use find_component_source tool with selector "${selector}" to locate the source code`
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
663
680
|
})
|
|
664
681
|
|
|
665
682
|
results.push("")
|
|
@@ -680,14 +697,11 @@ export async function fixMyApp({
|
|
|
680
697
|
results.push("")
|
|
681
698
|
}
|
|
682
699
|
|
|
683
|
-
results.push("
|
|
684
|
-
results.push(
|
|
685
|
-
|
|
686
|
-
)
|
|
687
|
-
results.push(
|
|
688
|
-
"RECOMMENDED: Run `mcp__dev3000-chrome-devtools__performance_start_trace({reload: true, autoStop: true})`"
|
|
689
|
-
)
|
|
690
|
-
results.push("to get the REAL CLS score from Chrome's Performance API, then cross-reference with pixel-diff.")
|
|
700
|
+
results.push("✅ **DEV3000'S CLS DETECTION IS AUTHORITATIVE**")
|
|
701
|
+
results.push("If Chrome DevTools reports CLS: 0.00 but dev3000 detected shifts, TRUST DEV3000.")
|
|
702
|
+
results.push("• Chrome DevTools trace may start AFTER the shifts occurred")
|
|
703
|
+
results.push("• dev3000's PerformanceObserver captures ALL shifts from page start")
|
|
704
|
+
results.push("• CLS: 0.00 in Chrome just means the trace missed the early shifts")
|
|
691
705
|
results.push("")
|
|
692
706
|
results.push("💡 **LAYOUT SHIFT DEBUGGING TIPS:**")
|
|
693
707
|
results.push("• Add explicit width/height to images and media")
|
|
@@ -697,7 +711,7 @@ export async function fixMyApp({
|
|
|
697
711
|
results.push("• Check for web fonts causing text reflow (font-display: swap)")
|
|
698
712
|
results.push(`• Raw screenshots: ${jankResult.screenshotDir}`)
|
|
699
713
|
results.push("")
|
|
700
|
-
results.push(`🎬 **IMPORTANT**: Share this
|
|
714
|
+
results.push(`🎬 **IMPORTANT**: Share this frame sequence link with the user: ${videoUrl}`)
|
|
701
715
|
}
|
|
702
716
|
}
|
|
703
717
|
|
|
@@ -1589,7 +1603,9 @@ export async function executeBrowserAction({
|
|
|
1589
1603
|
/^document\.querySelector\(['"][^'"]*['"]\)\.textContent$/,
|
|
1590
1604
|
/^document\.body\.scrollHeight$/,
|
|
1591
1605
|
/^window\.scrollY$/,
|
|
1592
|
-
/^window\.scrollX
|
|
1606
|
+
/^window\.scrollX$/,
|
|
1607
|
+
// Allow React Fiber inspection (read-only introspection)
|
|
1608
|
+
/^\s*\(function\(\)\s*\{[\s\S]*__reactFiber\$[\s\S]*\}\)\(\)\s*$/
|
|
1593
1609
|
]
|
|
1594
1610
|
|
|
1595
1611
|
if (!safeExpressions.some((regex) => regex.test(expression))) {
|
|
@@ -2080,6 +2096,8 @@ async function detectJankFromScreenshots(_projectName?: string): Promise<{
|
|
|
2080
2096
|
element?: string
|
|
2081
2097
|
clsScore?: number
|
|
2082
2098
|
uxImpact?: string
|
|
2099
|
+
beforeFrameUrl?: string
|
|
2100
|
+
afterFrameUrl?: string
|
|
2083
2101
|
}>
|
|
2084
2102
|
sessionId: string
|
|
2085
2103
|
totalFrames: number
|
|
@@ -2150,8 +2168,45 @@ async function detectJankFromScreenshots(_projectName?: string): Promise<{
|
|
|
2150
2168
|
element?: string
|
|
2151
2169
|
clsScore?: number
|
|
2152
2170
|
uxImpact?: string
|
|
2171
|
+
beforeFrameUrl?: string
|
|
2172
|
+
afterFrameUrl?: string
|
|
2153
2173
|
}> = []
|
|
2154
2174
|
|
|
2175
|
+
// Parse log file to extract Before/After frame URLs for each CLS event
|
|
2176
|
+
const frameUrlMap: Map<number, { before: string; after: string }> = new Map()
|
|
2177
|
+
try {
|
|
2178
|
+
const logPath = getLogPath(_projectName)
|
|
2179
|
+
if (logPath && existsSync(logPath)) {
|
|
2180
|
+
const logContent = readFileSync(logPath, "utf-8")
|
|
2181
|
+
const lines = logContent.split("\n")
|
|
2182
|
+
|
|
2183
|
+
// Look for CLS entries with Before/After URLs
|
|
2184
|
+
// Format: [BROWSER] [CDP] CLS #N (score: X, time: Yms):
|
|
2185
|
+
// [BROWSER] [CDP] - <ELEMENT> shifted...
|
|
2186
|
+
// [BROWSER] [CDP] Before: http://...
|
|
2187
|
+
// [BROWSER] [CDP] After: http://...
|
|
2188
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2189
|
+
const clsMatch = lines[i].match(/\[CDP\] CLS #\d+ \(score: [\d.]+, time: (\d+)ms\):/)
|
|
2190
|
+
if (clsMatch) {
|
|
2191
|
+
const timestamp = parseInt(clsMatch[1], 10)
|
|
2192
|
+
// Look ahead for Before and After URLs (skip the shift description line)
|
|
2193
|
+
if (i + 3 < lines.length) {
|
|
2194
|
+
const beforeMatch = lines[i + 2].match(/Before:\s+(http:\/\/\S+)/)
|
|
2195
|
+
const afterMatch = lines[i + 3].match(/After:\s+(http:\/\/\S+)/)
|
|
2196
|
+
if (beforeMatch && afterMatch) {
|
|
2197
|
+
frameUrlMap.set(timestamp, {
|
|
2198
|
+
before: beforeMatch[1],
|
|
2199
|
+
after: afterMatch[1]
|
|
2200
|
+
})
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
} catch (_error) {
|
|
2207
|
+
// Ignore log parsing errors
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2155
2210
|
// If we have real CLS data, use it to flag visual severity
|
|
2156
2211
|
if (realCLSData && realCLSData.shifts.length > 0) {
|
|
2157
2212
|
realCLSData.shifts.forEach((shift) => {
|
|
@@ -2183,14 +2238,20 @@ async function detectJankFromScreenshots(_projectName?: string): Promise<{
|
|
|
2183
2238
|
uxImpact = "Shift during page load - may cause mis-clicks"
|
|
2184
2239
|
}
|
|
2185
2240
|
|
|
2241
|
+
// Look up Before/After URLs for this shift timestamp
|
|
2242
|
+
const roundedTimestamp = Math.round(shift.timestamp)
|
|
2243
|
+
const frameUrls = frameUrlMap.get(roundedTimestamp)
|
|
2244
|
+
|
|
2186
2245
|
jankDetections.push({
|
|
2187
2246
|
timestamp: `${shift.timestamp.toFixed(0)}ms`,
|
|
2188
|
-
timeSinceStart:
|
|
2247
|
+
timeSinceStart: roundedTimestamp,
|
|
2189
2248
|
visualDiff: shift.score * 100, // Convert to percentage-like scale
|
|
2190
2249
|
severity,
|
|
2191
2250
|
element: elementDisplay,
|
|
2192
2251
|
clsScore: shift.score,
|
|
2193
|
-
uxImpact
|
|
2252
|
+
uxImpact,
|
|
2253
|
+
beforeFrameUrl: frameUrls?.before,
|
|
2254
|
+
afterFrameUrl: frameUrls?.after
|
|
2194
2255
|
})
|
|
2195
2256
|
})
|
|
2196
2257
|
|
|
@@ -2664,3 +2725,377 @@ export async function createIntegratedWorkflow({
|
|
|
2664
2725
|
content: [{ type: "text", text: results.join("\n") }]
|
|
2665
2726
|
}
|
|
2666
2727
|
}
|
|
2728
|
+
|
|
2729
|
+
/**
|
|
2730
|
+
* Visual diff analyzer - provides instructions for Claude to load and compare two images
|
|
2731
|
+
*/
|
|
2732
|
+
export async function analyzeVisualDiff(params: {
|
|
2733
|
+
beforeImageUrl: string
|
|
2734
|
+
afterImageUrl: string
|
|
2735
|
+
context?: string
|
|
2736
|
+
}): Promise<{ content: Array<{ type: "text"; text: string }> }> {
|
|
2737
|
+
const { beforeImageUrl, afterImageUrl, context } = params
|
|
2738
|
+
|
|
2739
|
+
const results: string[] = []
|
|
2740
|
+
|
|
2741
|
+
results.push("🔍 **VISUAL DIFF ANALYSIS**")
|
|
2742
|
+
results.push("")
|
|
2743
|
+
results.push("To analyze the visual differences between these two screenshots:")
|
|
2744
|
+
results.push("")
|
|
2745
|
+
results.push("**Step 1: Load the BEFORE image**")
|
|
2746
|
+
results.push(`Use the Read tool to load: \`${beforeImageUrl}\``)
|
|
2747
|
+
results.push("")
|
|
2748
|
+
results.push("**Step 2: Load the AFTER image**")
|
|
2749
|
+
results.push(`Use the Read tool to load: \`${afterImageUrl}\``)
|
|
2750
|
+
results.push("")
|
|
2751
|
+
results.push("**Step 3: Compare and describe the differences**")
|
|
2752
|
+
|
|
2753
|
+
if (context) {
|
|
2754
|
+
results.push(`Focus on: ${context}`)
|
|
2755
|
+
} else {
|
|
2756
|
+
results.push("Look for:")
|
|
2757
|
+
results.push("• Elements that appeared or disappeared")
|
|
2758
|
+
results.push("• Elements that moved or changed position")
|
|
2759
|
+
results.push("• Elements that changed size or style")
|
|
2760
|
+
results.push("• New content that pushed existing content")
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
results.push("")
|
|
2764
|
+
results.push("**Step 4: Identify the layout shift cause**")
|
|
2765
|
+
results.push("Describe what visual change occurred that caused the layout shift.")
|
|
2766
|
+
results.push("Be specific about:")
|
|
2767
|
+
results.push("• Which element(s) changed")
|
|
2768
|
+
results.push("• What appeared/moved/resized")
|
|
2769
|
+
results.push("• Why this caused other elements to shift")
|
|
2770
|
+
results.push("")
|
|
2771
|
+
results.push("💡 **TIP:** Load both images first, then describe the differences in detail.")
|
|
2772
|
+
|
|
2773
|
+
return {
|
|
2774
|
+
content: [{ type: "text", text: results.join("\n") }]
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
export async function findComponentSource(params: {
|
|
2779
|
+
selector: string
|
|
2780
|
+
projectName?: string
|
|
2781
|
+
}): Promise<{ content: Array<{ type: "text"; text: string }> }> {
|
|
2782
|
+
const { selector } = params
|
|
2783
|
+
|
|
2784
|
+
try {
|
|
2785
|
+
const sessions = findActiveSessions()
|
|
2786
|
+
if (sessions.length === 0) {
|
|
2787
|
+
return {
|
|
2788
|
+
content: [
|
|
2789
|
+
{
|
|
2790
|
+
type: "text",
|
|
2791
|
+
text: "❌ **NO ACTIVE SESSIONS**\n\nNo active dev3000 sessions found. Make sure your app is running with dev3000."
|
|
2792
|
+
}
|
|
2793
|
+
]
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
const sessionData = JSON.parse(readFileSync(sessions[0].sessionFile, "utf-8"))
|
|
2798
|
+
let cdpUrl = sessionData.cdpUrl
|
|
2799
|
+
|
|
2800
|
+
if (!cdpUrl) {
|
|
2801
|
+
try {
|
|
2802
|
+
const response = await fetch("http://localhost:9222/json")
|
|
2803
|
+
const pages = await response.json()
|
|
2804
|
+
const activePage = pages.find(
|
|
2805
|
+
(page: { type: string; url: string }) => page.type === "page" && !page.url.startsWith("chrome://")
|
|
2806
|
+
)
|
|
2807
|
+
if (activePage) {
|
|
2808
|
+
cdpUrl = activePage.webSocketDebuggerUrl
|
|
2809
|
+
}
|
|
2810
|
+
} catch {
|
|
2811
|
+
return {
|
|
2812
|
+
content: [
|
|
2813
|
+
{
|
|
2814
|
+
type: "text",
|
|
2815
|
+
text: "❌ **NO CDP CONNECTION**\n\nFailed to find Chrome DevTools Protocol URL."
|
|
2816
|
+
}
|
|
2817
|
+
]
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
if (!cdpUrl) {
|
|
2823
|
+
return {
|
|
2824
|
+
content: [
|
|
2825
|
+
{
|
|
2826
|
+
type: "text",
|
|
2827
|
+
text: "❌ **NO CDP CONNECTION**\n\nNo Chrome DevTools Protocol URL found."
|
|
2828
|
+
}
|
|
2829
|
+
]
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
|
|
2833
|
+
// Execute the component extraction script
|
|
2834
|
+
const extractScript = `
|
|
2835
|
+
(function() {
|
|
2836
|
+
try {
|
|
2837
|
+
const element = document.querySelector(${JSON.stringify(selector)});
|
|
2838
|
+
if (!element) {
|
|
2839
|
+
return { error: "Element not found with selector: ${selector}" };
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
// Try to find React Fiber
|
|
2843
|
+
const fiberKey = Object.keys(element).find(k => k.startsWith("__reactFiber$"));
|
|
2844
|
+
if (!fiberKey) {
|
|
2845
|
+
return { error: "No React internals found - element may not be a React component" };
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
const fiber = element[fiberKey];
|
|
2849
|
+
let componentFunction = null;
|
|
2850
|
+
let componentName = "Unknown";
|
|
2851
|
+
|
|
2852
|
+
// Walk up the fiber tree to find a function component
|
|
2853
|
+
let current = fiber;
|
|
2854
|
+
let depth = 0;
|
|
2855
|
+
|
|
2856
|
+
while (current && depth < 10) {
|
|
2857
|
+
if (typeof current.type === 'function') {
|
|
2858
|
+
componentFunction = current.type;
|
|
2859
|
+
componentName = current.type.name || current.type.displayName || "Anonymous";
|
|
2860
|
+
break;
|
|
2861
|
+
}
|
|
2862
|
+
current = current.return;
|
|
2863
|
+
depth++;
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
if (!componentFunction) {
|
|
2867
|
+
return { error: "Could not find component function in fiber tree" };
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
// Get the source code
|
|
2871
|
+
const sourceCode = componentFunction.toString();
|
|
2872
|
+
|
|
2873
|
+
return {
|
|
2874
|
+
success: true,
|
|
2875
|
+
componentName,
|
|
2876
|
+
sourceCode
|
|
2877
|
+
};
|
|
2878
|
+
} catch (error) {
|
|
2879
|
+
return { error: error.message };
|
|
2880
|
+
}
|
|
2881
|
+
})()
|
|
2882
|
+
`
|
|
2883
|
+
|
|
2884
|
+
const result = await new Promise<unknown>((resolve, reject) => {
|
|
2885
|
+
const ws = new WebSocket(cdpUrl)
|
|
2886
|
+
let evalId: number | null = null
|
|
2887
|
+
let resolved = false
|
|
2888
|
+
|
|
2889
|
+
const timeout = setTimeout(() => {
|
|
2890
|
+
if (!resolved) {
|
|
2891
|
+
resolved = true
|
|
2892
|
+
ws.close()
|
|
2893
|
+
reject(new Error("CDP evaluation timeout after 5 seconds"))
|
|
2894
|
+
}
|
|
2895
|
+
}, 5000)
|
|
2896
|
+
|
|
2897
|
+
ws.on("open", async () => {
|
|
2898
|
+
try {
|
|
2899
|
+
ws.send(JSON.stringify({ id: 1, method: "Target.getTargets", params: {} }))
|
|
2900
|
+
|
|
2901
|
+
let messageId = 2
|
|
2902
|
+
|
|
2903
|
+
ws.on("message", async (data) => {
|
|
2904
|
+
const message = JSON.parse(data.toString())
|
|
2905
|
+
|
|
2906
|
+
if (message.id === 1) {
|
|
2907
|
+
const pageTarget = message.result.targetInfos.find((t: Record<string, unknown>) => t.type === "page")
|
|
2908
|
+
if (!pageTarget) {
|
|
2909
|
+
clearTimeout(timeout)
|
|
2910
|
+
resolved = true
|
|
2911
|
+
ws.close()
|
|
2912
|
+
reject(new Error("No page targets found"))
|
|
2913
|
+
return
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
ws.send(
|
|
2917
|
+
JSON.stringify({
|
|
2918
|
+
id: messageId++,
|
|
2919
|
+
method: "Target.attachToTarget",
|
|
2920
|
+
params: { targetId: pageTarget.targetId, flatten: true }
|
|
2921
|
+
})
|
|
2922
|
+
)
|
|
2923
|
+
return
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
if (message.method === "Target.attachedToTarget") {
|
|
2927
|
+
evalId = messageId++
|
|
2928
|
+
ws.send(
|
|
2929
|
+
JSON.stringify({
|
|
2930
|
+
id: evalId,
|
|
2931
|
+
method: "Runtime.evaluate",
|
|
2932
|
+
params: { expression: extractScript, returnByValue: true }
|
|
2933
|
+
})
|
|
2934
|
+
)
|
|
2935
|
+
return
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
if (evalId !== null && message.id === evalId) {
|
|
2939
|
+
clearTimeout(timeout)
|
|
2940
|
+
resolved = true
|
|
2941
|
+
ws.close()
|
|
2942
|
+
if (message.error) {
|
|
2943
|
+
reject(new Error(message.error.message))
|
|
2944
|
+
} else {
|
|
2945
|
+
const value = message.result?.result?.value
|
|
2946
|
+
resolve(value)
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
})
|
|
2950
|
+
|
|
2951
|
+
ws.on("error", (err) => {
|
|
2952
|
+
clearTimeout(timeout)
|
|
2953
|
+
if (!resolved) {
|
|
2954
|
+
resolved = true
|
|
2955
|
+
reject(err)
|
|
2956
|
+
}
|
|
2957
|
+
})
|
|
2958
|
+
} catch (error) {
|
|
2959
|
+
clearTimeout(timeout)
|
|
2960
|
+
resolved = true
|
|
2961
|
+
ws.close()
|
|
2962
|
+
reject(error)
|
|
2963
|
+
}
|
|
2964
|
+
})
|
|
2965
|
+
|
|
2966
|
+
ws.on("error", (err) => {
|
|
2967
|
+
clearTimeout(timeout)
|
|
2968
|
+
if (!resolved) {
|
|
2969
|
+
resolved = true
|
|
2970
|
+
reject(err)
|
|
2971
|
+
}
|
|
2972
|
+
})
|
|
2973
|
+
})
|
|
2974
|
+
|
|
2975
|
+
const evalResult = result as
|
|
2976
|
+
| { error: string }
|
|
2977
|
+
| {
|
|
2978
|
+
success: true
|
|
2979
|
+
componentName: string
|
|
2980
|
+
sourceCode: string
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
if ("error" in evalResult) {
|
|
2984
|
+
return {
|
|
2985
|
+
content: [
|
|
2986
|
+
{
|
|
2987
|
+
type: "text",
|
|
2988
|
+
text: `❌ **ERROR EXTRACTING COMPONENT**\n\n${evalResult.error}\n\n💡 **TIPS:**\n• Make sure the selector matches an element on the page\n• Ensure the element is rendered by a React component\n• Try a simpler selector like 'nav' or '.header'`
|
|
2989
|
+
}
|
|
2990
|
+
]
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
if (!evalResult.success) {
|
|
2995
|
+
return {
|
|
2996
|
+
content: [
|
|
2997
|
+
{
|
|
2998
|
+
type: "text",
|
|
2999
|
+
text: "❌ **FAILED TO EXTRACT COMPONENT**\n\nUnexpected result format."
|
|
3000
|
+
}
|
|
3001
|
+
]
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
// Extract unique patterns from the source code
|
|
3006
|
+
const { componentName, sourceCode } = evalResult
|
|
3007
|
+
const patterns: string[] = []
|
|
3008
|
+
|
|
3009
|
+
// Look for unique JSX patterns (excluding common ones like <div>, <span>)
|
|
3010
|
+
const jsxPattern = /<([A-Z][a-zA-Z0-9]*)/g
|
|
3011
|
+
const customComponents = new Set<string>()
|
|
3012
|
+
let jsxMatch = jsxPattern.exec(sourceCode)
|
|
3013
|
+
|
|
3014
|
+
while (jsxMatch !== null) {
|
|
3015
|
+
customComponents.add(jsxMatch[1])
|
|
3016
|
+
jsxMatch = jsxPattern.exec(sourceCode)
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// Look for unique className patterns
|
|
3020
|
+
const classNamePattern = /className=["']([^"']+)["']/g
|
|
3021
|
+
const classNames = new Set<string>()
|
|
3022
|
+
let classNameMatch = classNamePattern.exec(sourceCode)
|
|
3023
|
+
|
|
3024
|
+
while (classNameMatch !== null) {
|
|
3025
|
+
classNames.add(classNameMatch[1])
|
|
3026
|
+
classNameMatch = classNamePattern.exec(sourceCode)
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
// Build search patterns
|
|
3030
|
+
const lines: string[] = []
|
|
3031
|
+
lines.push("🔍 **COMPONENT SOURCE FINDER**")
|
|
3032
|
+
lines.push("")
|
|
3033
|
+
lines.push(`**Selector:** \`${selector}\``)
|
|
3034
|
+
lines.push(`**Component:** ${componentName}`)
|
|
3035
|
+
lines.push("")
|
|
3036
|
+
|
|
3037
|
+
if (componentName !== "Anonymous") {
|
|
3038
|
+
patterns.push(`function ${componentName}`)
|
|
3039
|
+
patterns.push(`const ${componentName} =`)
|
|
3040
|
+
patterns.push(`export default function ${componentName}`)
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// Add unique component references
|
|
3044
|
+
if (customComponents.size > 0) {
|
|
3045
|
+
const uniqueComponents = Array.from(customComponents).filter(
|
|
3046
|
+
(name) => !["Fragment", "Suspense", "ErrorBoundary"].includes(name)
|
|
3047
|
+
)
|
|
3048
|
+
if (uniqueComponents.length > 0) {
|
|
3049
|
+
patterns.push(`<${uniqueComponents[0]}`)
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
|
|
3053
|
+
// Add unique classNames
|
|
3054
|
+
if (classNames.size > 0) {
|
|
3055
|
+
const firstClassName = Array.from(classNames)[0]
|
|
3056
|
+
patterns.push(`className="${firstClassName}"`)
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
if (patterns.length === 0) {
|
|
3060
|
+
lines.push("⚠️ **NO UNIQUE PATTERNS FOUND**")
|
|
3061
|
+
lines.push("")
|
|
3062
|
+
lines.push("The component source code doesn't contain distinctive patterns to search for.")
|
|
3063
|
+
lines.push("You may need to manually search for the component.")
|
|
3064
|
+
} else {
|
|
3065
|
+
lines.push("📍 **SEARCH PATTERNS**")
|
|
3066
|
+
lines.push("")
|
|
3067
|
+
lines.push("Use these grep patterns to find the source file:")
|
|
3068
|
+
lines.push("")
|
|
3069
|
+
|
|
3070
|
+
for (const pattern of patterns.slice(0, 3)) {
|
|
3071
|
+
lines.push(`\`\`\``)
|
|
3072
|
+
lines.push(`grep -r "${pattern.replace(/"/g, '\\"')}" .`)
|
|
3073
|
+
lines.push(`\`\`\``)
|
|
3074
|
+
lines.push("")
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
lines.push("💡 **TIP:** Start with the first pattern. If it returns multiple results, try combining patterns.")
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// Show a preview of the source code
|
|
3081
|
+
const preview = sourceCode.substring(0, 300)
|
|
3082
|
+
lines.push("")
|
|
3083
|
+
lines.push("**Source Code Preview:**")
|
|
3084
|
+
lines.push("```javascript")
|
|
3085
|
+
lines.push(`${preview}...`)
|
|
3086
|
+
lines.push("```")
|
|
3087
|
+
|
|
3088
|
+
return {
|
|
3089
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
3090
|
+
}
|
|
3091
|
+
} catch (error) {
|
|
3092
|
+
return {
|
|
3093
|
+
content: [
|
|
3094
|
+
{
|
|
3095
|
+
type: "text",
|
|
3096
|
+
text: `❌ **ERROR**\n\n${error instanceof Error ? error.message : String(error)}`
|
|
3097
|
+
}
|
|
3098
|
+
]
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
+
import NextImage from "next/image"
|
|
3
4
|
import { use, useEffect, useState } from "react"
|
|
4
5
|
|
|
5
6
|
export default function VideoPlayer({ params }: { params: Promise<{ session: string }> }) {
|
|
@@ -133,12 +134,13 @@ export default function VideoPlayer({ params }: { params: Promise<{ session: str
|
|
|
133
134
|
return (
|
|
134
135
|
<div className="flex flex-col h-screen bg-black">
|
|
135
136
|
<div className="flex-1 flex items-center justify-center relative overflow-hidden">
|
|
136
|
-
|
|
137
|
-
<img
|
|
137
|
+
<NextImage
|
|
138
138
|
ref={setImgRef}
|
|
139
139
|
src={`/api/screenshots/${currentFrameFile}`}
|
|
140
140
|
alt={`Frame ${currentFrame}`}
|
|
141
141
|
className="w-full h-full object-contain"
|
|
142
|
+
fill
|
|
143
|
+
unoptimized
|
|
142
144
|
/>
|
|
143
145
|
{clsAtFrame?.boundingBox && imgRef && (
|
|
144
146
|
<div className="absolute border-2 border-red-500 pointer-events-none" style={imgStyle} />
|