UNPKG

@versatil/sdlc-framework

Version:

🚀 AI-Native SDLC framework with 11-MCP ecosystem, RAG memory, OPERA orchestration, and 6 specialized agents achieving ZERO CONTEXT LOSS. Features complete CI/CD pipeline with 7 GitHub workflows (MCP testing, security scanning, performance benchmarking),

751 lines (748 loc) • 26.2 kB
/** * VERSATIL SDLC Framework - Ultimate UI/UX Orchestrator * Advanced frontend capabilities with Playwright, Chrome DevTools, shadcn, and more */ import { EventEmitter } from 'events'; import { VERSATILLogger } from '../utils/logger.js'; export class UltimateUIUXOrchestrator extends EventEmitter { constructor(paths) { super(); // MCP clients for UI/UX tools this.uiTools = { playwright: null, chrome: null, shadcn: null, figma: null }; // Testing configurations this.testConfig = { browsers: ['chromium', 'firefox', 'webkit'], devices: ['iPhone 12', 'iPad Pro', 'Desktop Chrome'], viewports: [ { width: 375, height: 667, name: 'Mobile' }, { width: 768, height: 1024, name: 'Tablet' }, { width: 1920, height: 1080, name: 'Desktop' } ], themes: ['light', 'dark', 'high-contrast'] }; // Component library this.componentLibrary = { shadcn: new Map(), custom: new Map(), patterns: new Map() }; this.logger = new VERSATILLogger('UIUXOrchestrator'); this.paths = paths; } async initialize() { // Initialize MCP connections await this.initializeMCPTools(); // Load component library await this.loadComponentLibrary(); // Load UI patterns await this.loadUIPatterns(); this.logger.info('Ultimate UI/UX orchestrator initialized'); } /** * Initialize MCP tools for UI/UX */ async initializeMCPTools() { // Initialize Playwright MCP try { const PlaywrightMCP = await import('@modelcontextprotocol/playwright-mcp'); this.uiTools.playwright = new PlaywrightMCP.Client({ browsers: this.testConfig.browsers, headless: true, slowMo: 0 }); this.logger.info('Connected to Playwright MCP'); } catch (error) { this.logger.warn('Playwright MCP not available'); } // Initialize Chrome DevTools MCP try { const ChromeMCP = await import('@modelcontextprotocol/chrome-mcp'); this.uiTools.chrome = new ChromeMCP.Client({ port: 9222, features: ['performance', 'accessibility', 'coverage', 'network'] }); this.logger.info('Connected to Chrome DevTools MCP'); } catch (error) { this.logger.warn('Chrome DevTools MCP not available'); } // Initialize shadcn MCP try { const ShadcnMCP = await import('@modelcontextprotocol/shadcn-mcp'); this.uiTools.shadcn = new ShadcnMCP.Client({ components: 'all', themes: this.testConfig.themes }); this.logger.info('Connected to shadcn MCP'); } catch (error) { this.logger.warn('shadcn MCP not available'); } } /** * Comprehensive UI/UX testing for a component */ async testUserExperience(component) { this.logger.info(`Testing user experience for ${component.name}`); const tests = await Promise.all([ this.testVisualRegression(component), this.testAccessibility(component), this.testPerformance(component), this.testUserFlows(component), this.testResponsiveness(component), this.testInteractions(component), this.testThemes(component), this.testAnimations(component) ]); const result = { score: this.calculateUXScore(tests), issues: this.extractIssues(tests), suggestions: await this.generateImprovements(tests, component), a11yReport: tests[1], performanceMetrics: tests[2], visualRegressions: tests[0], userFlowResults: tests[3] }; // Store results for learning await this.storeTestResults(component, result); return result; } /** * Test visual regression across browsers and themes */ async testVisualRegression(component) { if (!this.uiTools.playwright) return []; const regressions = []; for (const browser of this.testConfig.browsers) { for (const theme of this.testConfig.themes) { const result = await this.uiTools.playwright.compareScreenshots({ component: component.path, browser, theme, threshold: 0.01 // 1% difference threshold }); if (result.mismatchPercentage > 0.01) { regressions.push({ component: component.name, baseline: result.baseline, current: result.current, diff: result.diff, mismatchPercentage: result.mismatchPercentage }); } } } return regressions; } /** * Test accessibility with multiple tools */ async testAccessibility(component) { const violations = []; const warnings = []; let passes = 0; // Test with Playwright if (this.uiTools.playwright) { const playwrightA11y = await this.uiTools.playwright.runAccessibilityAudit({ url: component.path, standards: ['WCAG2AA', 'Section508'] }); violations.push(...playwrightA11y.violations); passes += playwrightA11y.passes; } // Test with Chrome DevTools if (this.uiTools.chrome) { const chromeA11y = await this.uiTools.chrome.runLighthouseAudit({ url: component.path, categories: ['accessibility'] }); // Parse Chrome results const chromeViolations = this.parseChromeA11y(chromeA11y); violations.push(...chromeViolations); } // Calculate score const score = passes / (passes + violations.length) * 100; return { score, violations, passes, warnings }; } /** * Test performance metrics */ async testPerformance(component) { if (!this.uiTools.chrome) { return this.getDefaultPerformanceMetrics(); } const metrics = await this.uiTools.chrome.collectPerformanceMetrics({ url: component.path, throttling: { cpuSlowdown: 4, network: 'Fast 3G' } }); return { FCP: metrics.firstContentfulPaint, LCP: metrics.largestContentfulPaint, CLS: metrics.cumulativeLayoutShift, TTI: metrics.timeToInteractive, TBT: metrics.totalBlockingTime, SI: metrics.speedIndex }; } /** * Test user flows with real interactions */ async testUserFlows(component) { if (!this.uiTools.playwright) return []; const flows = []; // Define common user flows const userFlows = [ { name: 'Basic Navigation', steps: [ { action: 'navigate', target: component.path }, { action: 'click', target: '[data-testid="nav-menu"]' }, { action: 'click', target: '[data-testid="nav-item-1"]' }, { action: 'wait', duration: 1000 }, { action: 'screenshot', name: 'navigation-complete' } ] }, { name: 'Form Submission', steps: [ { action: 'navigate', target: component.path }, { action: 'fill', target: 'input[name="email"]', value: 'test@example.com' }, { action: 'fill', target: 'input[name="password"]', value: 'password123' }, { action: 'click', target: 'button[type="submit"]' }, { action: 'wait', selector: '[data-testid="success-message"]' }, { action: 'screenshot', name: 'form-submitted' } ] }, { name: 'Interactive Elements', steps: [ { action: 'navigate', target: component.path }, { action: 'hover', target: '[data-testid="dropdown"]' }, { action: 'click', target: '[data-testid="dropdown-item-2"]' }, { action: 'keyboard', key: 'Tab' }, { action: 'keyboard', key: 'Enter' }, { action: 'screenshot', name: 'interaction-complete' } ] } ]; // Execute flows for (const flow of userFlows) { const result = await this.executeUserFlow(flow, component); flows.push(result); } return flows; } /** * Execute a single user flow */ async executeUserFlow(flow, component) { const startTime = Date.now(); const steps = []; const screenshots = []; const issues = []; let success = true; for (const step of flow.steps) { const stepStart = Date.now(); let stepSuccess = true; let error; try { switch (step.action) { case 'navigate': await this.uiTools.playwright.navigate(step.target); break; case 'click': await this.uiTools.playwright.click(step.target); break; case 'fill': await this.uiTools.playwright.fill(step.target, step.value); break; case 'hover': await this.uiTools.playwright.hover(step.target); break; case 'wait': if (step.selector) { await this.uiTools.playwright.waitForSelector(step.selector); } else if (step.duration) { await new Promise(resolve => setTimeout(resolve, step.duration)); } break; case 'screenshot': const screenshot = await this.uiTools.playwright.screenshot(); screenshots.push(screenshot); break; case 'keyboard': await this.uiTools.playwright.keyboard(step.key); break; } } catch (err) { stepSuccess = false; success = false; error = err.message; issues.push(`Step "${step.action}" failed: ${error}`); } steps.push({ name: step.action, success: stepSuccess, duration: Date.now() - stepStart, error }); } return { flowName: flow.name, steps, success, duration: Date.now() - startTime, screenshots, issues }; } /** * Test responsiveness across devices */ async testResponsiveness(component) { if (!this.uiTools.playwright) return { issues: [] }; const issues = []; for (const viewport of this.testConfig.viewports) { const result = await this.uiTools.playwright.testViewport({ url: component.path, viewport, checks: [ 'no-horizontal-scroll', 'readable-text', 'clickable-elements', 'proper-spacing' ] }); if (result.issues.length > 0) { issues.push(...result.issues.map((issue) => ({ type: 'responsive', severity: 'medium', description: `${viewport.name}: ${issue.description}`, element: issue.selector, recommendation: issue.fix }))); } } return { issues }; } /** * Test interactions and animations */ async testInteractions(component) { if (!this.uiTools.chrome) return { smooth: true }; const interactionMetrics = await this.uiTools.chrome.recordInteractions({ url: component.path, interactions: [ { type: 'hover', selector: 'button' }, { type: 'click', selector: 'button' }, { type: 'scroll', distance: 500 }, { type: 'swipe', direction: 'left' } ], metrics: ['fps', 'jank', 'cpu', 'memory'] }); return { smooth: interactionMetrics.avgFps > 55, fps: interactionMetrics.avgFps, jank: interactionMetrics.jankCount, issues: interactionMetrics.issues }; } /** * Test theme switching and consistency */ async testThemes(component) { const issues = []; for (const theme of this.testConfig.themes) { // Test theme application if (this.uiTools.shadcn) { const themeResult = await this.uiTools.shadcn.applyTheme({ component: component.name, theme, validate: true }); if (!themeResult.valid) { issues.push({ type: 'visual', severity: 'medium', description: `Theme "${theme}" has inconsistencies`, recommendation: 'Ensure all color variables are properly defined' }); } } // Test contrast ratios if (this.uiTools.chrome) { const contrastResult = await this.uiTools.chrome.checkColorContrast({ url: component.path, theme }); contrastResult.violations.forEach((violation) => { issues.push({ type: 'accessibility', severity: 'high', description: `Low contrast in ${theme} theme: ${violation.text}`, element: violation.selector, recommendation: `Increase contrast ratio to at least ${violation.required}` }); }); } } return { issues }; } /** * Test animations and transitions */ async testAnimations(component) { if (!this.uiTools.chrome) return { smooth: true }; const animationMetrics = await this.uiTools.chrome.profileAnimations({ url: component.path, duration: 5000, // Record for 5 seconds interactions: ['hover', 'click', 'scroll'] }); return { smooth: animationMetrics.droppedFrames < 5, fps: animationMetrics.averageFps, droppedFrames: animationMetrics.droppedFrames, longTasks: animationMetrics.longTasks }; } /** * Generate improvement suggestions using AI */ async generateImprovements(tests, component) { const suggestions = []; // Analyze test results const issues = this.extractIssues(tests); // Performance improvements const perfMetrics = tests[2]; if (perfMetrics.LCP > 2500) { suggestions.push({ type: 'performance', description: 'Optimize Largest Contentful Paint', impact: 'high', implementation: 'Use next/image for images, implement lazy loading, optimize critical CSS', example: ` import Image from 'next/image'; <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority placeholder="blur" /> `.trim() }); } // Accessibility improvements const a11yReport = tests[1]; if (a11yReport.score < 90) { suggestions.push({ type: 'accessibility', description: 'Improve accessibility score', impact: 'high', implementation: 'Add proper ARIA labels, ensure keyboard navigation, fix color contrast', example: ` <button aria-label="Open navigation menu" aria-expanded={isOpen} onClick={toggleMenu} > <MenuIcon aria-hidden="true" /> </button> `.trim() }); } // Responsive improvements const responsiveIssues = issues.filter(i => i.type === 'responsive'); if (responsiveIssues.length > 0) { suggestions.push({ type: 'responsive', description: 'Fix responsive design issues', impact: 'medium', implementation: 'Use CSS Grid/Flexbox, add proper media queries, test on real devices', example: ` .container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem; @media (max-width: 768px) { grid-template-columns: 1fr; } } `.trim() }); } // Animation improvements const animationData = tests[7]; if (animationData && !animationData.smooth) { suggestions.push({ type: 'animation', description: 'Optimize animations for 60fps', impact: 'medium', implementation: 'Use transform instead of position, add will-change, use GPU acceleration', example: ` .animated-element { will-change: transform; transform: translateZ(0); /* GPU acceleration */ transition: transform 0.3s ease-out; } .animated-element:hover { transform: translateY(-4px) translateZ(0); } `.trim() }); } // Component optimization with shadcn if (this.uiTools.shadcn) { const shadcnSuggestions = await this.uiTools.shadcn.suggestOptimizations({ component: component.name, issues: issues.map(i => i.description) }); suggestions.push(...shadcnSuggestions); } return suggestions; } /** * Create optimized UI component */ async createOptimizedComponent(spec) { this.logger.info(`Creating optimized component: ${spec.name}`); // Use shadcn MCP to create component if (this.uiTools.shadcn) { const component = await this.uiTools.shadcn.createComponent({ name: spec.name, type: spec.type, theme: spec.theme || 'default', features: { accessibility: spec.accessibility !== false, animations: spec.animations !== false, responsive: true, darkMode: true } }); // Test the component immediately const testResult = await this.testUserExperience({ name: spec.name, path: component.path }); // Apply automatic fixes if (testResult.issues.length > 0) { const fixed = await this.applyAutoFixes(component, testResult.issues); component.code = fixed.code; } // Store in component library this.componentLibrary.shadcn.set(spec.name, component); return component; } throw new Error('shadcn MCP not available'); } /** * Apply automatic fixes to component */ async applyAutoFixes(component, issues) { let code = component.code; for (const issue of issues) { switch (issue.type) { case 'accessibility': code = this.fixAccessibilityIssue(code, issue); break; case 'performance': code = this.fixPerformanceIssue(code, issue); break; case 'responsive': code = this.fixResponsiveIssue(code, issue); break; } } return { code }; } /** * Fix accessibility issue in code */ fixAccessibilityIssue(code, issue) { // Simple fixes - in production would use AST manipulation if (issue.description.includes('missing alt text')) { code = code.replace(/<img([^>]*)>/g, (match, attrs) => { if (!attrs.includes('alt=')) { return `<img${attrs} alt="">`; } return match; }); } if (issue.description.includes('missing aria-label')) { code = code.replace(/<button([^>]*)>/g, (match, attrs) => { if (!attrs.includes('aria-label=')) { return `<button${attrs} aria-label="Button">`; } return match; }); } return code; } /** * Fix performance issue in code */ fixPerformanceIssue(code, issue) { // Add lazy loading to images if (issue.description.includes('images')) { code = code.replace(/<img([^>]*)>/g, (match, attrs) => { if (!attrs.includes('loading=')) { return `<img${attrs} loading="lazy">`; } return match; }); } return code; } /** * Fix responsive issue in code */ fixResponsiveIssue(code, issue) { // Add responsive classes if (issue.description.includes('overflow')) { code = code.replace(/className="([^"]*)"/g, (match, classes) => { if (!classes.includes('overflow-')) { return `className="${classes} overflow-x-auto"`; } return match; }); } return code; } /** * Helper methods */ calculateUXScore(tests) { let totalScore = 0; let weights = 0; // Accessibility: 30% const a11y = tests[1]; if (a11y) { totalScore += a11y.score * 0.3; weights += 0.3; } // Performance: 25% const perf = tests[2]; if (perf) { const perfScore = this.calculatePerformanceScore(perf); totalScore += perfScore * 0.25; weights += 0.25; } // Visual: 20% const visual = tests[0]; if (visual) { const visualScore = visual.length === 0 ? 100 : Math.max(0, 100 - visual.length * 10); totalScore += visualScore * 0.2; weights += 0.2; } // User flows: 15% const flows = tests[3]; if (flows && flows.length > 0) { const flowScore = (flows.filter(f => f.success).length / flows.length) * 100; totalScore += flowScore * 0.15; weights += 0.15; } // Responsiveness: 10% const responsive = tests[4]; if (responsive) { const respScore = responsive.issues ? Math.max(0, 100 - responsive.issues.length * 10) : 100; totalScore += respScore * 0.1; weights += 0.1; } return weights > 0 ? Math.round(totalScore / weights) : 0; } calculatePerformanceScore(metrics) { // Based on Core Web Vitals thresholds let score = 100; if (metrics.LCP > 2500) score -= 20; else if (metrics.LCP > 4000) score -= 40; if (metrics.FCP > 1800) score -= 15; else if (metrics.FCP > 3000) score -= 30; if (metrics.CLS > 0.1) score -= 15; else if (metrics.CLS > 0.25) score -= 30; if (metrics.TBT > 300) score -= 10; else if (metrics.TBT > 600) score -= 20; return Math.max(0, score); } extractIssues(tests) { const issues = []; // Extract from each test type for (const test of tests) { if (test && test.issues) { issues.push(...test.issues); } } // Sort by severity return issues.sort((a, b) => { const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; return severityOrder[a.severity] - severityOrder[b.severity]; }); } parseChromeA11y(results) { // Parse Chrome accessibility audit results return []; } getDefaultPerformanceMetrics() { return { FCP: 0, LCP: 0, CLS: 0, TTI: 0, TBT: 0, SI: 0 }; } async storeTestResults(component, result) { // Store for future learning and pattern detection this.emit('test:completed', { component, result }); } async loadComponentLibrary() { // Load existing components this.logger.debug('Loading component library'); } async loadUIPatterns() { // Load UI patterns for reuse this.logger.debug('Loading UI patterns'); } /** * Cleanup */ async shutdown() { // Disconnect MCP tools for (const tool of Object.values(this.uiTools)) { if (tool && tool.disconnect) { await tool.disconnect(); } } } } //# sourceMappingURL=ultimate-ui-ux-orchestrator.js.map