Guia de Estilo e Padrões
Este documento define as convenções de código estritas e os requisitos de qualidade para o projeto tupynambalucas.dev. Todo o código deve aderir a estes padrões para garantir a consistência e a manutenibilidade em todo o monorepo.
1. Formatação de Código (Prettier)
O código deve seguir as regras definidas em shared/config/prettierrc.json:
- Recuo (Indentation): 2 espaços.
- Ponto e vírgula: Sempre usar (
true). - Aspas: Usar aspas simples (
true), exceto em JSX. - Vírgula final (Trailing Comma): Sempre usar onde for possível (
all). - Largura da linha: Máximo de 100 caracteres.
2. Regras do TypeScript (Modo Estrito)
Priorizamos a máxima segurança de tipos por meio de uma configuração rigorosa do TypeScript.
- Definições de Objetos: Usar
interfacepara definições de objetos para garantir consistência. Atalhos de tipo (type aliases) são permitidos para uniões ou tipos utilitários (ex:z.infer<>). - Matrizes (Arrays): Usar a sintaxe
T[]em vez deArray<T>. - Importações de Tipo: Sempre usar
import typepara tipos. Importe tipos separadamente dos valores (style: separate-type-imports). - Variáveis Não Utilizadas: Prefixar com um sublinhado (ex:
_id) para sinalizar intenção. - Tipagem Estrita: O uso de
anyé estritamente proibido. - Comparações: Sempre usar igualdade estrita (
===). - Booleanos Estritos: 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 type guards 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á é do tipoT). O linter irá sinalizar isso; remova-as para manter o código limpo.
3. Gerenciamento 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 sinaliza uma 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 manter a conformidade comrequire-awaitenquanto preserva a integridade dos tipos. - Tratamento de Erros: Toda operação com
awaitdeve estar dentro de um blocotry/catchou parte de uma cadeia.catch().
4. Padrões Específicos de Pacotes
4.1. Pacotes Core (@tupynambalucas-hub/core / @tupynambalucas-hub/core)
- Zero Avisos: Estes pacotes devem ter zero avisos (warnings) ou erros do linter.
- APIs Públicas: Todos os membros exportados devem ter tipos de retorno definidos explicitamente.
4.2. API (Fastify)
- Nomenclatura: Controllers e Services usam
PascalCasepara classes ecamelCasepara métodos. - Mapeamento: Os Controllers são responsáveis por mapear modelos de banco de dados para DTOs do Core via métodos privados.
4.3. Web (React)
- Padrões do React 19: Use o hook
use()para consumir promessas e contexto quando aplicável, reduzindo o boilerplate douseEffect. Prefira Server Actions (se aplicável) ou propriedadesactionotimizadas em formulários para uma melhor experiência do usuário. - Estado Global: Acione 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últiplos refs para um componente filho, passe-os individualmente em vez de em um objeto agrupado para evitar confusão do linter e garantir clareza. - Booleanos Estritos na UI: Em JSX, sempre use comparações explícitas:
{isValid === true && <Component />}para evitar a renderização de0ouNaNna tela. - Sincronização de Estado: Evite chamar
setStatesincronamente dentro douseEffectse a atualização for derivada de propriedades (props) ou de 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 (List Keys): Sempre use IDs estáveis e exclusivos (ex:
_id) como chaves em listas. Evite usar índices de array, a menos que a lista seja estática e não tenha identificadores exclusivos. Se os índices precisarem ser usados, documente o motivo. - Estilização & Medidas Responsivas: Use CSS Modules (
.module.css). Classes utilitárias do Tailwind são permitidas dentro de módulos via@apply. Adira estritamente aos Padrões Responsivos definidos na Seção 5. - Logs no Console:
console.logé proibido e causará falha na build de produção. Useconsole.info/warn/errorcom moderação.
5. CSS & Padrões Responsivos (Estritos)
Para garantir uma interface de usuário fluida, acessível e moderna, as seguintes regras são obrigatórias para todas as aplicações:
- Sem Pixels (
px): O uso de valores fixos empxé estritamente proibido para dimensionamento, espaçamento e tipografia.- Exceção:
1pxé permitido para bordas quando se deseja um efeito de linha fina (hairline).
- 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 usar media queries para cada pequeno ajuste.
Exemplos Responsivos
.container {
/* Largura fluida: min 320px, preferencial 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 do viewport menos a altura do cabeçalho (assumido 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 de código fonte.
7. Padrões de Idioma (Estritamente Inglês Primeiro)
Para manter a acessibilidade global, escalabilidade e consistência do código em todos os contextos do monorepo:
- Código Fonte & 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 relatórios arquitetônicos 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 Conventional Commits e ser escritos em inglês (consulte Convenções de Commit).
- Exceções de Localização & 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 fictícios (mocks) explícitos que representam texto do usuário final em português. Toda a lógica interna do aplicativo e 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) é EXCLUSIVAMENTE reservado 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 de API: Visualizando como os modelos de domínio devem ser estruturados.
8.1. Exemplo de Padrão Rigoroso de React & CSS Module
O exemplo abaixo demonstra nosso padrão preferido para componentes interativos usando CSS Modules. Ele mostra a integração de gerenciamento de estado, memoização e manipulação assíncrona enquanto adere estritamente ao nosso TypeScript, lógica booleana e regras de estilização.
.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 * Demonstra: CSS Modules (objeto styles), useCallback, operador void, booleanos explícitos e chaves estáveis. */ // import styles from './AdvancedCycleManager.module.css'; function AdvancedCycleManager() { // No desenvolvimento real, 'styles' vem da importação do CSS Module acima. // Nesta demonstração ao vivo, mapeamos as chaves para os nomes de classe simulados definidos 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('Products submitted:', products); alert('Cycle products updated successfully!'); } catch (err) { console.error('[Update Error]:', 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> {/* Internal CSS for the demo purpose only - NOT for production */} <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