@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
JavaScript
/**
* 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