Style Guide and Standards
Este documento define as convenções estritas de codificação e requisitos de qualidade para o projeto Elo Orgânico. Todo o código deve aderir a estes padrões para garantir consistência e manutenibilidade em todo o monorepo.
1. Formatação de Código (Prettier)
Code must follow the rules defined in .prettierrc:
- Indentação: 2 espaços.
- Ponto e vírgula: Sempre utilizar (true).
- Aspas: Utilizar aspas simples (true), exceto em JSX.
- Trailing Comma: Sempre utilizar onde for possível (all).
- Line Width: Máximo de 100 caracteres.
2. Regras de TypeScript (Modo Estrito)
We prioritize maximum type safety through a rigorous TypeScript configuration.
- Definições de Objetos: Use
interfacepara definições de objetos para garantir consistência. Aliases de tipo são permitidos para uniões ou tipos utilitários (ex:z.infer<>). - Arrays: Use a sintaxe
T[]em vez deArray<T>. - Importação de Tipos: Sempre use
import typepara tipos. Importe tipos separadamente de valores (style: separate-type-imports). - Variáveis Não Utilizadas: Prefixar com um underscore (ex:
_id) para sinalizar intenção. - Tipagem Estrita: O uso de
anyé estritamente proibido. - Comparações: Sempre use igualdade estrita (
===). - Strict Booleans: Booleanas devem ser explícitas. Use
if (value !== undefined)em vez deif (value). - Conversões Seguras: Não use
String(value)or${value}em tipos genéricosunknownouobject. Use guards de tipo explícitos para primitivos para evitar bugs do tipo[object Object]. - Asserções de Tipo: Evite asserções de tipo desnecessárias (ex:
value as Tquandovaluejá éT). O linter sinalizará isso; remova-os para manter o código limpo.
3. Gestão de Código Assíncrono (Crítico)
- No Floating Promises: Nunca deixe promessas "flutuando" sem tratamento ou await.
- O Operador
void: Quando uma função assíncrona é chamada por seus efeitos colaterais e NÃO é aguardada (awaited), prefixe-a comvoid(ex:void startServer()). Isso sinaliza execução não bloqueante intencional. - Handlers do Fastify: Handlers de rota devem usar o tipo
FastifyZodHandlere retornarPromise<void>. Usevoid reply.send()quando não retornar a resposta diretamente. - Plugins do Fastify: Se um
FastifyPluginAsyncnão usarawait, remova a palavra-chaveasynce retornePromise.resolve()para cumprir com orequire-awaitmantendo a integridade do tipo. - Error Handling: Cada operação
awaitdeve estar dentro de um blocotry/catchou fazer parte de uma cadeia.catch().
4. Padrões Específicos por Pacote
4.1. Pacotes Core (@elo-instance/core / @elo-portal/core)
- Zero Warnings: Estes pacotes devem ter zero avisos ou erros de lint.
- Public APIs: All exported members must have explicitly defined return types.
4.2. API (Fastify)
- Naming: Controllers e Services usam
PascalCasepara classes ecamelCasefor métodos. - Mapping: Controllers são responsáveis por mapear modelos de banco de dados para DTOs do Core através de métodos privados.
4.3. Web (React)
- Padrões do React 19: Use o hook
use()para consumir promises e contexto quando aplicável, reduzindo o boilerplate deuseEffect. Prefira Server Actions (se aplicável) ou propsactionotimizadas em formulários para uma melhor experiência de usuário. - Global State: Dispare efeitos colaterais assíncronos em Stores ou Effects usando o operador
voidpara chamadas não aguardadas. - Refs: Acessar
ref.currentdurante a renderização é estritamente proibido. Ao passar múltiplas refs para um componente filho, passe-as individualmente em vez de em um objeto agrupado para evitar confusão do linter. - Strict Booleans na UI: No JSX, sempre use comparações explícitas:
{isValid === true && <Component />}para evitar a renderização acidental de0ouNaNna interface. - Sincronização de Estado: Evite chamar
setStatede forma síncrona dentro deuseEffectse a atualização for derivada de props ou outro estado. Em vez disso, sincronize o estado durante a renderização (o padrão "prevProps") ou inicialize o estado com uma função. - Chaves de Lista (Keys): Sempre use IDs estáveis e únicos (ex:
_id) como chaves em listas. Evite usar índices de array, a menos que a lista seja estática. - Estilização e Medidas Responsivas: Use CSS Modules (
.module.css). Classes utilitárias do Tailwind são permitidas dentro dos módulos via@apply. Crucialmente, o uso de medidas fixas (ex:px) é estritamente proibido para garantir um layout fluido e acessível. Sempre use unidades responsivas:rempara tipografia e espaçamento (onde1rem = 16px),%,vh/vwpara layouts estruturais, e funções CSS comoclamp()para escalonamento fluido. - Console Logs:
console.logé proibido e causará falha no build de produção. Useconsole.info/warn/errorcom moderação.
5. Naming Conventions
- Schemas: Devem terminar com
Schema(ex:ProductSchema). - Arquivos: Seguir o padrão
nome.tipo.ts(ex:auth.controller.ts,apiPlugin.ts). - Directories: Usar
camelCasepara nomes de diretórios dentro das pastas fonte.
6. Padrões de Blocos de Código (blocos live)
O bloco de código live do Docusaurus (editor interativo) é reservado EXCLUSIVAMENTE para este Guia de Estilo. Ele NÃO deve ser usado em nenhuma outra página de documentação.
Seu propósito é estritamente demonstrar:
- Componentes React: Seguindo padrões rigorosos (booleanos explícitos, tipos estritos, etc.).
- Estruturas de Domínio da API: Visualizando como os modelos de domínio devem ser estruturados.
6.1. Exemplo de Padrão React Rigoroso
O exemplo abaixo demonstra nosso padrão preferido para componentes interativos. Ele mostra a integração de gestão de estado, memoização e tratamento assíncrono enquanto adere estritamente às nossas regras de TypeScript e lógica booleana.
/** * Gerenciador Avançado de Produtos do Ciclo * Demonstra: Interfaces, useCallback, operador void, booleanos explícitos e chaves estáveis. */ function AdvancedCycleManager() { // 1. Regra: Inicialização de estado explícita e lógica booleana estrita // interface IProductItem { id: string; name: string; price: number; isValid: boolean; } const [products, setProducts] = React.useState([ { id: 'uuid-1', name: 'Alface Crespa', price: 4.5, isValid: true }, { id: 'uuid-2', name: 'Tomate Cereja', price: 0, isValid: false }, ]); const [isSubmitting, setIsSubmitting] = React.useState(false); // 2. Regra: useMemo/useCallback para estado derivado complexo ou handlers const hasErrors = React.useMemo(() => { return products.some((p) => p.isValid === false); }, [products]); const updateProductPrice = React.useCallback((id, value) => { const numericValue = parseFloat(value) || 0; setProducts((prev) => prev.map((p) => (p.id === id ? { ...p, price: numericValue, isValid: numericValue > 0 } : p)), ); }, []); // 3. Regra: Gestão de Código Assíncrono (Crítico) const handleSubmit = async () => { setIsSubmitting(true); try { // Simular chamada de API para demonstração await new Promise((resolve) => setTimeout(resolve, 1500)); console.info('Produtos enviados:', products); alert('Produtos do ciclo atualizados com sucesso!'); } catch (err) { console.error('[Erro de Atualização]:', err); } finally { setIsSubmitting(false); } }; // Handler de evento seguindo o Guia de Estilo const handleAction = () => { // Regra: Prefixar chamadas assíncronas não aguardadas com 'void' para sinalizar execução não bloqueante void handleSubmit(); }; return ( <div style={{ padding: '1.5rem', border: '1px solid #e2e8f0', borderRadius: '12px', backgroundColor: '#ffffff', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', }} > <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '1.5rem', alignItems: 'center', }} > <h3 style={{ margin: 0, color: '#1e293b' }}>Gerenciador de Ciclo</h3> {/* Regra: Comparações Booleanas Explícitas (=== true) */} <span style={{ fontSize: '0.7rem', fontWeight: '800', padding: '0.3rem 0.8rem', borderRadius: '20px', letterSpacing: '0.05em', backgroundColor: hasErrors === true ? '#fee2e2' : '#f0fdf4', color: hasErrors === true ? '#991b1b' : '#166534', }} > {hasErrors === true ? '⚠ ITENS INVÁLIDOS' : '✓ PRONTO PARA SALVAR'} </span> </div> <div style={{ display: 'flex', flexDirection: 'column', gap: '0.8rem', marginBottom: '1.5rem' }} > {products.map((product) => ( <div key={product.id} // Regra: Sempre use IDs estáveis e únicos como chaves style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0.8rem', borderRadius: '8px', border: `1px solid ${product.isValid === false ? '#fecaca' : '#f1f5f9'}`, backgroundColor: product.isValid === false ? '#fff1f2' : '#fafafa', }} > <div> <div style={{ fontWeight: '700', fontSize: '0.9rem', color: '#334155' }}> {product.name} </div> <div style={{ fontSize: '0.7rem', color: '#94a3b8' }}> Identificador: {product.id} </div> </div> <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}> <span style={{ fontSize: '0.8rem', fontWeight: '600', color: '#64748b' }}>R$</span> <input type="number" value={product.price} onChange={(e) => updateProductPrice(product.id, e.target.value)} style={{ width: '80px', padding: '0.4rem', borderRadius: '6px', border: '1px solid #cbd5e1', textAlign: 'right', fontSize: '0.9rem', }} /> </div> </div> ))} </div> <button // Regra: Verificação booleana explícita em props (disabled={... === true}) disabled={isSubmitting === true || hasErrors === true} onClick={handleAction} style={{ width: '100%', padding: '1rem', borderRadius: '8px', border: 'none', backgroundColor: '#16a34a', color: 'white', fontWeight: '700', fontSize: '0.95rem', cursor: isSubmitting === true || hasErrors === true ? 'not-allowed' : 'pointer', opacity: isSubmitting === true || hasErrors === true ? 0.6 : 1, transition: 'background-color 0.2s', }} > {isSubmitting === true ? 'Processando Atualização...' : 'Salvar Alterações do Ciclo'} </button> {/* Nota: Console.info é permitido para depuração; console.log é proibido. */} </div> ); }
Última Atualização: Junho de 2026