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.
Files changed (108) hide show
  1. package/dist/cdp-monitor.d.ts.map +1 -1
  2. package/dist/cdp-monitor.js +40 -30
  3. package/dist/cdp-monitor.js.map +1 -1
  4. package/dist/cli.js +12 -12
  5. package/dist/cli.js.map +1 -1
  6. package/dist/constants/log-colors.d.ts +20 -23
  7. package/dist/constants/log-colors.d.ts.map +1 -1
  8. package/dist/constants/log-colors.js +25 -26
  9. package/dist/constants/log-colors.js.map +1 -1
  10. package/dist/dev-environment.d.ts.map +1 -1
  11. package/dist/dev-environment.js +15 -20
  12. package/dist/dev-environment.js.map +1 -1
  13. package/dist/screencast-manager.d.ts +0 -1
  14. package/dist/screencast-manager.d.ts.map +1 -1
  15. package/dist/screencast-manager.js +47 -9
  16. package/dist/screencast-manager.js.map +1 -1
  17. package/dist/src/tui-interface-impl.tsx +43 -26
  18. package/dist/tui-interface-impl.d.ts.map +1 -1
  19. package/dist/tui-interface-impl.js +35 -22
  20. package/dist/tui-interface-impl.js.map +1 -1
  21. package/mcp-server/.next/BUILD_ID +1 -1
  22. package/mcp-server/.next/build-manifest.json +5 -5
  23. package/mcp-server/.next/fallback-build-manifest.json +2 -2
  24. package/mcp-server/.next/prerender-manifest.json +3 -3
  25. package/mcp-server/.next/server/app/_global-error/page/build-manifest.json +3 -3
  26. package/mcp-server/.next/server/app/_global-error/page.js.nft.json +1 -1
  27. package/mcp-server/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  28. package/mcp-server/.next/server/app/_global-error.html +2 -2
  29. package/mcp-server/.next/server/app/_global-error.rsc +1 -1
  30. package/mcp-server/.next/server/app/_not-found/page/build-manifest.json +3 -3
  31. package/mcp-server/.next/server/app/_not-found/page.js.nft.json +1 -1
  32. package/mcp-server/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  33. package/mcp-server/.next/server/app/_not-found.html +1 -1
  34. package/mcp-server/.next/server/app/_not-found.rsc +2 -2
  35. package/mcp-server/.next/server/app/index.html +1 -1
  36. package/mcp-server/.next/server/app/index.rsc +3 -3
  37. package/mcp-server/.next/server/app/logs/page/build-manifest.json +3 -3
  38. package/mcp-server/.next/server/app/logs/page.js.nft.json +1 -1
  39. package/mcp-server/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  40. package/mcp-server/.next/server/app/mcp/route.js +2 -2
  41. package/mcp-server/.next/server/app/page/build-manifest.json +3 -3
  42. package/mcp-server/.next/server/app/page.js.nft.json +1 -1
  43. package/mcp-server/.next/server/app/page_client-reference-manifest.js +1 -1
  44. package/mcp-server/.next/server/app/video/[session]/page/build-manifest.json +3 -3
  45. package/mcp-server/.next/server/app/video/[session]/page.js.nft.json +1 -1
  46. package/mcp-server/.next/server/app/video/[session]/page_client-reference-manifest.js +1 -1
  47. package/mcp-server/.next/server/chunks/[root-of-the-server]__00592d3f._.js +64 -7
  48. package/mcp-server/.next/server/chunks/[root-of-the-server]__00592d3f._.js.map +1 -1
  49. package/mcp-server/.next/server/chunks/[root-of-the-server]__177c72c6._.js +5 -23
  50. package/mcp-server/.next/server/chunks/[root-of-the-server]__177c72c6._.js.map +1 -1
  51. package/mcp-server/.next/server/chunks/d1d76_next_dist_esm_build_templates_app-route_820fc951.js +1 -1
  52. package/mcp-server/.next/server/chunks/d1d76_next_dist_esm_build_templates_app-route_820fc951.js.map +1 -1
  53. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__22d96eb1._.js +3 -0
  54. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__22d96eb1._.js.map +1 -0
  55. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__63dd19ce._.js +3 -0
  56. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__63dd19ce._.js.map +1 -0
  57. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__94d8e211._.js +3 -0
  58. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__94d8e211._.js.map +1 -0
  59. package/mcp-server/.next/server/chunks/ssr/{[root-of-the-server]__0217e89c._.js → [root-of-the-server]__cf17e26a._.js} +2 -2
  60. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__cf17e26a._.js.map +1 -0
  61. package/mcp-server/.next/server/chunks/ssr/_96ba1d8e._.js +8 -0
  62. package/mcp-server/.next/server/chunks/ssr/_96ba1d8e._.js.map +1 -0
  63. package/mcp-server/.next/server/chunks/ssr/_aa32a659._.js +3 -0
  64. package/mcp-server/.next/server/chunks/ssr/_aa32a659._.js.map +1 -0
  65. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_b2368b47._.js +3 -0
  66. package/mcp-server/.next/server/chunks/ssr/node_modules__pnpm_b2368b47._.js.map +1 -0
  67. package/mcp-server/.next/server/middleware-build-manifest.js +3 -3
  68. package/mcp-server/.next/server/server-reference-manifest.js +1 -1
  69. package/mcp-server/.next/server/server-reference-manifest.json +1 -1
  70. package/mcp-server/.next/static/chunks/{3d37ed424c6aaf63.css → 0cf268b98f36b0b5.css} +1 -1
  71. package/mcp-server/.next/static/chunks/4b6eda65c6f5aada.js +1 -0
  72. package/mcp-server/.next/static/chunks/55cedb1a993e951c.js +1 -0
  73. package/mcp-server/.next/static/chunks/5fdb882fe407a798.js +1 -0
  74. package/mcp-server/.next/static/chunks/7abbb9a17b62c1fa.js +1 -0
  75. package/mcp-server/.next/static/chunks/98e04adc8a00688a.js +1 -0
  76. package/mcp-server/.next/static/chunks/db42008ac1e97815.js +1 -0
  77. package/mcp-server/.next/static/chunks/f08166f7946c2e06.js +1 -0
  78. package/mcp-server/.next/static/chunks/{turbopack-9656e7304584cab2.js → turbopack-7112e6b6aee1d84f.js} +1 -1
  79. package/mcp-server/app/api/jank/[session]/route.ts +1 -1
  80. package/mcp-server/app/logs/LogsClient.tsx +37 -37
  81. package/mcp-server/app/mcp/route.ts +26 -112
  82. package/mcp-server/app/mcp/tools.ts +455 -20
  83. package/mcp-server/app/video/[session]/page.tsx +4 -2
  84. package/package.json +1 -1
  85. package/src/tui-interface-impl.tsx +43 -26
  86. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__0217e89c._.js.map +0 -1
  87. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__91510608._.js +0 -3
  88. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__91510608._.js.map +0 -1
  89. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e1bc1b8a._.js +0 -3
  90. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__e1bc1b8a._.js.map +0 -1
  91. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__fb40854a._.js +0 -3
  92. package/mcp-server/.next/server/chunks/ssr/[root-of-the-server]__fb40854a._.js.map +0 -1
  93. package/mcp-server/.next/server/chunks/ssr/_62451611._.js +0 -3
  94. package/mcp-server/.next/server/chunks/ssr/_62451611._.js.map +0 -1
  95. package/mcp-server/.next/server/chunks/ssr/_b15f05ee._.js +0 -3
  96. package/mcp-server/.next/server/chunks/ssr/_b15f05ee._.js.map +0 -1
  97. package/mcp-server/.next/server/chunks/ssr/_bacf0748._.js +0 -8
  98. package/mcp-server/.next/server/chunks/ssr/_bacf0748._.js.map +0 -1
  99. package/mcp-server/.next/static/chunks/274a8d03fad7f819.js +0 -1
  100. package/mcp-server/.next/static/chunks/543e14c771a22442.js +0 -1
  101. package/mcp-server/.next/static/chunks/58fdd5192b305065.js +0 -1
  102. package/mcp-server/.next/static/chunks/6bd684c2018a357c.js +0 -1
  103. package/mcp-server/.next/static/chunks/6d59e588420330ca.js +0 -1
  104. package/mcp-server/.next/static/chunks/9625e4da85a132f3.js +0 -1
  105. package/mcp-server/.next/static/chunks/c36bc797d535a4dc.js +0 -1
  106. /package/mcp-server/.next/static/{xyH2s6kJYyYw2j2TSlqpE → A1zsECiQync8Ag1Q1mulr}/_buildManifest.js +0 -0
  107. /package/mcp-server/.next/static/{xyH2s6kJYyYw2j2TSlqpE → A1zsECiQync8Ag1Q1mulr}/_clientMiddlewareManifest.json +0 -0
  108. /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
- discover_available_mcps:
24
- "🔍 **PROACTIVE MCP DISCOVERY** - Automatically discover other MCPs running on the system using process detection and port pinging. No need to manually specify which MCPs are available!\n\n🎯 **DISCOVERY METHODS:**\n• Process Detection: Scans running processes for known MCP patterns\n Port Pinging: Tests standard MCP ports with HTTP/WebSocket health checks\n• Cross-Platform: Works on macOS, Linux, and Windows\n\n⚡ **SMART DETECTION:**\n• Detects nextjs-dev, chrome-devtools, and other common MCPs\n• Fallback from process detection to port pinging\n• Logs all discovery attempts for transparency\n\n💡 **PERFECT FOR:** 'What MCPs are available?' or when you want dev3000 to automatically find and integrate with other debugging tools!",
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
- get_mcp_capabilities:
27
- "🔍 **MCP CAPABILITY INSPECTOR** - Discover and inspect the current capabilities of available MCPs (dev3000-chrome-devtools and dev3000-nextjs-dev). Shows dynamically discovered functions with descriptions and categories. Perfect for understanding what enhanced capabilities are available for augmented delegation.\n\n **DYNAMIC DISCOVERY:**\n• Introspects MCP logs and schemas to find available functions\n• Categorizes capabilities as 'advanced' vs 'basic'\n• Generates intelligent descriptions based on function names\n• Caches results for performance (5min TTL)\n\n🎯 **REAL-TIME UPDATES:**\n• Automatically adapts when MCPs add new capabilities\n• No manual maintenance required\n• Always shows current state of available tools\n\n💡 **PERFECT FOR:** Understanding what enhanced capabilities are currently available, debugging MCP integration issues, or planning augmented debugging workflows."
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(`📹 **Watch video analysis**: ${videoUrl}`)
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("⚠️ **IMPORTANT: VALIDATE WITH CHROME DEVTOOLS**")
684
- results.push(
685
- "The pixel-diff analysis above may include false positives (images loading, position:fixed elements)."
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 video link with the user: ${videoUrl}`)
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: Math.round(shift.timestamp),
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
- {/* eslint-disable-next-line @next/next/no-img-element */}
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} />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev3000",
3
- "version": "0.0.80",
3
+ "version": "0.0.82",
4
4
  "description": "AI-powered development tools with browser monitoring and MCP server integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",