ai-debug-local-mcp
Version:
šÆ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
240 lines (229 loc) ⢠9.94 kB
JavaScript
import { BaseToolHandler } from '../base-handler.js';
export class FlutterWidgetTreeHandler extends BaseToolHandler {
localEngine;
quantumDebugger;
constructor(localEngine, quantumDebugger) {
super();
this.localEngine = localEngine;
this.quantumDebugger = quantumDebugger;
}
get tools() {
return [
{
name: 'flutter_widget_tree',
description: 'Get the Flutter widget tree for debugging Flutter Web applications. Shows widget hierarchy, properties, and render objects.',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' }
},
required: ['sessionId']
}
},
{
name: 'flutter_inspect_widget',
description: 'Inspect a specific Flutter widget by ID to see its properties and state.',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
widgetId: { type: 'string', description: 'Widget ID to inspect' },
highlight: { type: 'boolean', description: 'Highlight the widget in the UI', default: false }
},
required: ['sessionId', 'widgetId']
}
},
{
name: 'flutter_widget_tree_analysis',
description: 'Analyze widget tree for inefficient rebuilds, missing const constructors, deep nesting, and RepaintBoundary opportunities.',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' }
},
required: ['sessionId']
}
}
];
}
async handle(toolName, args, sessions) {
const session = sessions.get(args.sessionId);
if (!session) {
throw new Error('Session not found');
}
if (session.framework !== 'flutter' && session.framework !== 'flutter-web') {
throw new Error('This tool only works with Flutter Web applications');
}
switch (toolName) {
case 'flutter_widget_tree':
return this.getWidgetTree(session);
case 'flutter_inspect_widget':
if (!args.widgetId) {
throw new Error('Widget ID is required');
}
return this.inspectWidget(session, args.widgetId, args.highlight);
case 'flutter_widget_tree_analysis':
return this.analyzeWidgetTree(session);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
async getWidgetTree(session) {
const tree = await session.page.evaluate(() => {
// @ts-ignore
if (!window._flutterWidgetInspector) {
return { error: 'Widget inspector not available' };
}
const buildTree = (node, depth = 0) => {
if (!node || depth > 20)
return null;
return {
type: node.description || node.runtimeType,
id: node.id,
properties: node.properties || {},
children: node.children?.map((child) => buildTree(child, depth + 1)) || []
};
};
// @ts-ignore
const root = window._flutterWidgetInspector.getRootWidget();
const tree = buildTree(root);
// Count widgets and max depth
let totalWidgets = 0;
let maxDepth = 0;
const analyze = (node, depth = 0) => {
if (!node)
return;
totalWidgets++;
maxDepth = Math.max(maxDepth, depth);
node.children?.forEach((child) => analyze(child, depth + 1));
};
analyze(tree);
return {
root: tree,
totalWidgets,
maxDepth
};
});
if (!tree) {
return this.createErrorResponse(new Error('Failed to get widget tree'));
}
if (tree.error) {
return this.createErrorResponse(new Error(`Flutter Widget Tree Error: ${tree.error}`));
}
return this.createTextResponse(`š³ Flutter Widget Tree:
⢠Total Widgets: ${tree.totalWidgets}
⢠Max Depth: ${tree.maxDepth}
Root Widget:
${this.formatWidgetTree(tree.root)}
`);
}
formatWidgetTree(node, indent = '') {
if (!node)
return '';
let result = `${indent}āā ${node.type}${node.id ? ` (#${node.id})` : ''}`;
if (Object.keys(node.properties || {}).length > 0) {
result += ` (${Object.entries(node.properties)
.map(([k, v]) => `${k}: ${v}`)
.join(', ')})`;
}
result += '\n';
if (node.children?.length > 0) {
node.children.forEach((child, index) => {
const isLast = index === node.children.length - 1;
result += this.formatWidgetTree(child, indent + (isLast ? ' ' : 'ā '));
});
}
return result;
}
async inspectWidget(session, widgetId, highlight = false) {
const info = await session.page.evaluate((id, shouldHighlight) => {
// @ts-ignore
if (!window._flutterWidgetInspector) {
return { error: 'Widget inspector not available' };
}
// @ts-ignore
const widget = window._flutterWidgetInspector.getWidget(id);
if (!widget) {
return { error: 'Widget not found' };
}
// Highlight if requested
if (shouldHighlight) {
// @ts-ignore
window._flutterWidgetInspector.highlight(widget);
}
return {
widget: {
type: widget.description || widget.runtimeType,
id: widget.id,
properties: widget.properties || {},
state: widget.state || {},
renderObject: widget.renderObject ? {
size: widget.renderObject.size,
constraints: widget.renderObject.constraints,
paintBounds: widget.renderObject.paintBounds
} : null
},
highlighted: shouldHighlight
};
}, widgetId, highlight);
if (!info) {
return this.createErrorResponse(new Error('Failed to inspect widget'));
}
if (info.error) {
return this.createErrorResponse(new Error(`Widget Inspector Error: ${info.error}`));
}
return this.createTextResponse(`š Widget Inspector - ${info.widget.type} (#${info.widget.id}):
Properties:
${info.widget.properties && Object.keys(info.widget.properties).length > 0
? Object.entries(info.widget.properties)
.map(([k, v]) => `⢠${k}: ${JSON.stringify(v)}`)
.join('\n')
: '⢠None'}
State:
${info.widget.state && Object.keys(info.widget.state).length > 0
? Object.entries(info.widget.state)
.map(([k, v]) => `⢠${k}: ${JSON.stringify(v)}`)
.join('\n')
: '⢠No state'}
${info.widget.renderObject ? `
Render Object:
⢠Size: ${info.widget.renderObject.size?.width || 0} à ${info.widget.renderObject.size?.height || 0}
⢠Constraints: ${JSON.stringify(info.widget.renderObject.constraints || {})}
⢠Paint Bounds: ${JSON.stringify(info.widget.renderObject.paintBounds || {})}
` : 'No render object'}
${info.highlighted ? '⨠Widget highlighted in UI' : ''}
`);
}
async analyzeWidgetTree(session) {
const analysis = await session.page.evaluate(() => {
const issues = {
inefficientRebuilds: [],
missingConst: [],
deepNesting: [],
repaintBoundaries: []
};
// Mock analysis - in real implementation would traverse widget tree
// This is placeholder logic that would need Flutter internals access
return issues;
});
const totalIssues = Object.values(analysis)
.reduce((sum, arr) => sum + arr.length, 0);
return this.createTextResponse(`š Widget Tree Analysis:
Found ${totalIssues} optimization opportunities:
${analysis.inefficientRebuilds.length > 0 ? `
š Inefficient Rebuilds (${analysis.inefficientRebuilds.length}):
${analysis.inefficientRebuilds.map((issue) => `⢠${issue.widget}: ${issue.reason}\n ā ${issue.suggestion}`).join('\n')}` : ''}
${analysis.missingConst.length > 0 ? `
š Missing Const Constructors (${analysis.missingConst.length}):
${analysis.missingConst.map((issue) => `⢠${issue.widget} in ${issue.location} (Impact: ${issue.impact})`).join('\n')}` : ''}
${analysis.deepNesting.length > 0 ? `
šļø Deep Nesting Issues (${analysis.deepNesting.length}):
${analysis.deepNesting.map((issue) => `⢠${issue.path} (Depth: ${issue.depth})\n ā ${issue.suggestion}`).join('\n')}` : ''}
${analysis.repaintBoundaries.length > 0 ? `
šØ RepaintBoundary Opportunities (${analysis.repaintBoundaries.length}):
${analysis.repaintBoundaries.map((issue) => `⢠${issue.widget}: ${issue.suggestion} (Impact: ${issue.impact})`).join('\n')}` : ''}
${totalIssues === 0 ? 'ā
No major optimization issues found!' : ''}
`);
}
}
//# sourceMappingURL=flutter-widget-tree-handler.js.map