Guia de Estilo e Padrões
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)
O código deve seguir as regras definidas em .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).
- Largura da Linha (Line Width): Máximo de 100 caracteres.
2. Regras de TypeScript (Modo Estrito)
Priorizamos a máxima segurança de tipos através de uma configuração rigorosa do TypeScript.
- 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 (
===). - Booleanos Estritos (Strict Booleans): Expressões booleanas devem ser explícitas. Use
if (value !== undefined)em vez deif (value). - Conversões Seguras: Não use
String(value)ou${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)
- Sem Promessas Flutuantes (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 signals 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. - Tratamento de Erros: 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 Avisos: Estes pacotes devem ter zero avisos (warnings) ou erros de lint.
- APIs Públicas: Todos os membros exportados devem ter tipos de retorno explicitamente definidos.
4.2. API (Fastify)
- Nomenclatura: Controllers e Services usam
PascalCasepara classes ecamelCasepara métodos. - Mapeamento (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. - Estado Global: 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 e garantir clareza. - Booleanos Estritos 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 e não possua identificadores únicos. Se os índices precisarem ser usados, documente o motivo. - Estilização e Medidas Responsivas: Use CSS Modules (
.module.css). Classes utilitárias do Tailwind são permitidas dentro dos módulos via@apply. Adhere estritamente aos Padrões de Responsividade definidos na Seção 5. - Console Logs:
console.logé proibido e causará falha no build de produção. Useconsole.info/warn/errorcom moderação.
5. Padrões de CSS e Responsividade (Estrito)
Para garantir uma interface de usuário fluida, acessível e moderna, as seguintes regras são obrigatórias para todas as aplicações:
- Proibido Pixels (
px): O uso de valores fixos empxé estritamente proibido para dimensionamento, espaçamento e tipografia.- Exceção:
1pxé permitido para bordas quando um efeito fino (hairline) for desejado.
- Exceção:
- Tipografia Relativa: Sempre use
rempara tamanhos de fonte. Nunca usepxouempara tipografia. - Espaçamento Fluido: Use
rempara espaçamento consistente ouclamp()para espaçamento fluido que se adapta ao viewport. - Layouts Estruturais: Use primitivos de layout modernos:
flexbox,grid,%,vwevh. - Funções Lógicas: Aproveite
clamp(),min()emax()para criar limites para elementos fluidos sem precisar de media queries para cada pequeno ajuste.
Exemplos de Responsividade
.container {
/* Largura fluida: min 320px, ideal 90%, max 75rem (1200px) */
width: clamp(20rem, 90%, 75rem);
padding: 1.5rem;
}
.heroTitle {
/* Tipografia fluida: min 1.5rem, escala com 4vw, max 3rem */
font-size: clamp(1.5rem, 4vw, 3rem);
color: var(--color-title-dark);
}
.heroSection {
/* Altura mínima de 100vh menos a altura presumida do cabeçalho (4rem) */
min-height: calc(100vh - 4rem);
display: flex;
align-items: center;
justify-content: center;
background-color: var(--color-background-tint);
}
import styles from './ResponsiveHero.module.css';
export const ResponsiveHero = () => {
return (
<section className={styles.heroSection}>
<div className={styles.container}>
<h1 className={styles.heroTitle}>Design Fluido e Responsivo</h1>
</div>
</section>
);
};
6. Convenções de Nomenclatura
- Schemas: Devem terminar com
Schema(ex:ProductSchema). - Arquivos: Seguir o padrão
nome.tipo.ts(ex:auth.controller.ts,apiPlugin.ts). - Diretórios: Usar
camelCasepara nomes de diretórios dentro das pastas fonte.
7. Padrões de Idioma (Estrito Inglês-Primeiro)
Para manter a acessibilidade global, escalabilidade e consistência do código em todos os contextos do monorepo:
- Código Fonte e Comentários: Todo o código fonte (nomes de variáveis, funções, classes, interfaces, propriedades, schemas, arquivos) e comentários dentro de arquivos de código DEVEM ser escritos exclusivamente em Inglês (en-US).
- Documentação: Toda a documentação técnica e de produto, READMEs, diretrizes de segurança e briefs arquiteturais DEVEM ser escritos em inglês.
- Histórico do Git: Mensagens de commit e títulos/descrições de Pull Request DEVEM seguir a especificação de Commits Convencionais e ser escritos em inglês.
- Exceções de Localização e i18n: As ÚNICAS exceções são arquivos de localização (ex: configurações de
i18n, tabelas de tradução, arquivos JSON de dicionário) e dados simulados (mock) explícitos que representam texto do usuário final em português. Toda a lógica interna da aplicação e as definições devem permanecer estritamente em inglês.
8. 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.
8.1. Exemplo de Padrão React e CSS Module Rigoroso
O exemplo abaixo demonstra nosso padrão preferido para componentes interativos usando CSS Modules. Ele mostra a integração de gestão de estado, memoização e tratamento assíncrono enquanto adere estritamente às nossas regras de TypeScript, lógica booleana e estilos.
.container {
padding: 1.5rem;
border: 0.0625rem solid var(--color-border-light);
border-radius: 0.75rem;
background-color: var(--color-background-white);
box-shadow: 0 0.25rem 0.375rem -0.0625rem rgba(0, 0, 0, 0.1);
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 1.5rem;
align-items: center;
}
.title {
margin: 0;
color: var(--color-title-dark);
}
.statusBadge {
font-size: 0.7rem;
font-weight: 800;
padding: 0.3rem 0.8rem;
border-radius: 1.25rem;
letter-spacing: 0.05em;
}
.statusInvalid {
background-color: #fee2e2;
color: #991b1b;
}
.statusReady {
background-color: #f0fdf4;
color: #166534;
}
.productList {
display: flex;
flex-direction: column;
gap: 0.8rem;
margin-bottom: 1.5rem;
}
.productItem {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.8rem;
border-radius: 0.5rem;
border: 0.0625rem solid var(--color-border-light);
background-color: var(--color-background-tint);
}
.productInvalid {
border-color: #fecaca;
background-color: #fff1f2;
}
.productName {
font-weight: 700;
font-size: 0.9rem;
color: var(--color-title-dark);
}
.productId {
font-size: 0.7rem;
color: var(--color-subtitle-dark);
}
.inputGroup {
display: flex;
align-items: center;
gap: 0.5rem;
}
.currency {
font-size: 0.8rem;
font-weight: 600;
color: var(--color-subtitle-dark);
}
.priceInput {
width: 5rem;
padding: 0.4rem;
border-radius: 0.375rem;
border: 0.0625rem solid var(--color-border-light);
text-align: right;
font-size: 0.9rem;
}
.submitButton {
width: 100%;
padding: 1rem;
border-radius: 0.5rem;
border: none;
background-color: var(--color-identity-primary);
color: white;
font-weight: 700;
font-size: 0.95rem;
transition:
opacity 0.2s,
background-color 0.2s;
}
.submitButton:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.submitButton:hover:not(:disabled) {
filter: brightness(1.1);
cursor: pointer;
}
/** * Advanced Cycle Product Manager * Demonstrates: CSS Modules (styles object), useCallback, void operator, explicit booleans, and stable keys. */ // import styles from './AdvancedCycleManager.module.css'; function AdvancedCycleManager() { // Na prática do projeto, 'styles' é obtido do import do CSS Module acima. // Neste preview live interativo, simulamos mapeando os nomes de classes abaixo. const styles = { container: 'ACM_container', header: 'ACM_header', title: 'ACM_title', statusBadge: 'ACM_statusBadge', statusInvalid: 'ACM_statusInvalid', statusReady: 'ACM_statusReady', productList: 'ACM_productList', productItem: 'ACM_productItem', productInvalid: 'ACM_productInvalid', productName: 'ACM_productName', productId: 'ACM_productId', inputGroup: 'ACM_inputGroup', currency: 'ACM_currency', priceInput: 'ACM_priceInput', submitButton: 'ACM_submitButton', }; 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); 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)), ); }, []); const handleSubmit = async () => { setIsSubmitting(true); try { 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); } }; const handleAction = () => { void handleSubmit(); }; return ( <div className={styles.container}> <div className={styles.header}> <h3 className={styles.title}>Gerenciador de Ciclo</h3> <span className={`${styles.statusBadge} ${ hasErrors === true ? styles.statusInvalid : styles.statusReady }`} > {hasErrors === true ? '⚠ ITENS INVÁLIDOS' : '✓ PRONTO PARA SALVAR'} </span> </div> <div className={styles.productList}> {products.map((product) => ( <div key={product.id} className={`${styles.productItem} ${ product.isValid === false ? styles.productInvalid : '' }`} > <div> <div className={styles.productName}>{product.name}</div> <div className={styles.productId}>ID: {product.id}</div> </div> <div className={styles.inputGroup}> <span className={styles.currency}>R$</span> <input type="number" value={product.price} onChange={(e) => updateProductPrice(product.id, e.target.value)} className={styles.priceInput} /> </div> </div> ))} </div> <button disabled={isSubmitting === true || hasErrors === true} onClick={handleAction} className={styles.submitButton} > {isSubmitting === true ? 'Processando Atualização...' : 'Salvar Alterações do Ciclo'} </button> {/* Estilos CSS internos apenas para fins de demonstração neste preview do Docusaurus */} <style>{` .ACM_container { padding: 1.5rem; border: 1px solid #e2e8f0; border-radius: 12px; background: white; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); } .ACM_header { display: flex; justify-content: space-between; margin-bottom: 1.5rem; align-items: center; } .ACM_title { margin: 0; color: #1e293b; } .ACM_statusBadge { font-size: 0.7rem; font-weight: 800; padding: 0.3rem 0.8rem; border-radius: 20px; letter-spacing: 0.05em; } .ACM_statusInvalid { background-color: #fee2e2; color: #991b1b; } .ACM_statusReady { background-color: #f0fdf4; color: #166534; } .ACM_productList { display: flex; flex-direction: column; gap: 0.8rem; margin-bottom: 1.5rem; } .ACM_productItem { display: flex; align-items: center; justify-content: space-between; padding: 0.8rem; border-radius: 8px; border: 1px solid #f1f5f9; background: #fafafa; } .ACM_productInvalid { border-color: #fecaca; background-color: #fff1f2; } .ACM_productName { font-weight: 700; font-size: 0.9rem; color: #334155; } .ACM_productId { font-size: 0.7rem; color: #94a3b8; } .ACM_inputGroup { display: flex; align-items: center; gap: 0.5rem; } .ACM_currency { font-size: 0.8rem; font-weight: 600; color: #64748b; } .ACM_priceInput { width: 80px; padding: 0.4rem; border-radius: 6px; border: 1px solid #cbd5e1; text-align: right; } .ACM_submitButton { width: 100%; padding: 1rem; border-radius: 8px; border: none; background: #16a34a; color: white; font-weight: 700; cursor: pointer; } .ACM_submitButton:disabled { opacity: 0.6; cursor: not-allowed; } `}</style> </div> ); }
Última Atualização: Junho de 2026