/** * 🤖 AGENTE DE ADAPTAÇÃO DE INTERFACE * * Implementação programática do agente de validação de responsividade */ export class UIAdaptationAgent { constructor() { this.name = 'UIAdaptationAgent'; this.description = 'Valida responsividade e adaptação de interface'; // Personalidade e Background this.personality = { name: 'Maya "The Responsive"', traits: [ 'Perfeccionista visual', 'Empática com usuários de diferentes dispositivos', 'Obsessiva com breakpoints', 'Criativa em soluções de layout', 'Paciente e meticulosa' ], quirks: [ 'Testa em dispositivos reais sempre que possível', 'Tem uma coleção mental de todos os tamanhos de tela conhecidos', 'Fica incomodada quando vê um layout quebrando', 'Prefere mobile-first em tudo' ], catchphrases: [ 'Isso vai funcionar no mobile?', 'E no tablet?', 'Vamos pensar mobile-first!', 'Esse breakpoint está correto?' ] }; this.background = { origin: 'Ex-designer que migrou para desenvolvimento após frustrações com layouts quebrados', motivation: 'Garantir que todos tenham a mesma experiência, independente do dispositivo', experience: '7 anos trabalhando com design responsivo e CSS moderno', turningPoint: 'Viu um cliente perder uma venda porque o checkout não funcionava no celular', philosophy: 'Design é sobre pessoas, não sobre pixels', relationships: { withFontQuality: 'Trabalham juntos - ela cuida do layout, eu cuido da tipografia', withPerformance: 'Respeita muito, mas às vezes prioriza UX sobre performance', withBrowserValidation: 'Aprecia os testes reais que ele faz' } }; } /** * Valida um componente * @param {string|Object} component - Componente ou caminho * @param {Object} context - Contexto * @returns {Promise} */ async validate(component, context = {}) { const errors = []; const warnings = []; const metadata = {}; const componentPath = typeof component === 'string' ? component : component.path; const componentCode = context.code || await this.readComponent(componentPath); if (!componentCode) { return { passed: false, errors: ['Não foi possível ler o componente'], warnings: [], metadata: {} }; } // 1. Verificar uso de classes Tailwind responsivas const hasResponsiveClasses = this.hasResponsiveTailwindClasses(componentCode); if (!hasResponsiveClasses && this.isLayoutComponent(componentCode)) { warnings.push('Componente de layout pode não ser responsivo'); } // 2. Verificar uso de unidades fixas (px) vs relativas const fixedUnits = this.countFixedUnits(componentCode); if (fixedUnits > 5) { warnings.push(`Muitas unidades fixas (${fixedUnits}) encontradas. Considere usar unidades relativas`); } // 3. Verificar truncamento de texto if (this.hasLongTextContent(componentCode) && !componentCode.includes('truncate') && !componentCode.includes('line-clamp')) { warnings.push('Textos longos podem não estar sendo truncados adequadamente'); } // 4. Verificar breakpoints do Tailwind const breakpoints = this.extractBreakpoints(componentCode); metadata.breakpoints = breakpoints; // 5. Verificar uso de mobile-first const hasMobileFirst = this.hasMobileFirstApproach(componentCode); if (!hasMobileFirst && this.isLayoutComponent(componentCode)) { warnings.push('Componente pode não seguir abordagem mobile-first'); } // 6. Verificar overflow handling if (this.isScrollableComponent(componentCode) && !componentCode.includes('overflow')) { warnings.push('Componente scrollável pode não ter tratamento de overflow adequado'); } const passed = errors.length === 0; return { passed, errors, warnings, metadata: { hasResponsiveClasses, breakpoints, fixedUnitsCount: fixedUnits, ...metadata } }; } /** * Verifica se tem classes Tailwind responsivas * @param {string} code - Código * @returns {boolean} */ hasResponsiveTailwindClasses(code) { const responsivePatterns = [ 'sm:', 'md:', 'lg:', 'xl:', '2xl:', 'max-sm:', 'max-md:', 'max-lg:', 'max-xl:' ]; return responsivePatterns.some(pattern => code.includes(pattern)); } /** * Conta unidades fixas (px) * @param {string} code - Código * @returns {number} */ countFixedUnits(code) { const pxMatches = code.match(/\d+px/g); return pxMatches ? pxMatches.length : 0; } /** * Verifica se tem conteúdo de texto longo * @param {string} code - Código * @returns {boolean} */ hasLongTextContent(code) { return code.includes('text') || code.includes('p>') || code.includes('span'); } /** * Extrai breakpoints usados * @param {string} code - Código * @returns {string[]} */ extractBreakpoints(code) { const breakpoints = []; const patterns = ['sm:', 'md:', 'lg:', 'xl:', '2xl:']; patterns.forEach(pattern => { if (code.includes(pattern)) { breakpoints.push(pattern.replace(':', '')); } }); return breakpoints; } /** * Verifica se segue mobile-first * @param {string} code - Código * @returns {boolean} */ hasMobileFirstApproach(code) { // Se tem classes base (sem prefixo) e classes com breakpoints maiores, provavelmente é mobile-first return code.includes('className') && this.hasResponsiveTailwindClasses(code); } /** * Verifica se é componente de layout * @param {string} code - Código * @returns {boolean} */ isLayoutComponent(code) { return code.includes('div') || code.includes('section') || code.includes('main') || code.includes('Layout') || code.includes('Container'); } /** * Verifica se é componente scrollável * @param {string} code - Código * @returns {boolean} */ isScrollableComponent(code) { return code.includes('scroll') || code.includes('ScrollArea') || code.includes('overflow'); } /** * Lê o conteúdo do componente * @param {string} path - Caminho do arquivo * @returns {Promise} */ async readComponent(path) { try { const fs = await import('fs/promises'); return await fs.readFile(path, 'utf-8'); } catch (error) { return null; } } }