Pular para o conteúdo principal

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 interface para 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 de Array<T>.
  • Importações de Tipo: Sempre usar import type para 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 de if (value).
  • Conversões Seguras: Não use String(value) ou ${value} em tipos genéricos unknown ou object. 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 T quando value já é do tipo T). 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 com void (ex: void startServer()). Isso sinaliza uma execução não bloqueante intencional.
  • Handlers do Fastify: Handlers de rota devem usar o tipo FastifyZodHandler e retornar Promise<void>. Use void reply.send() quando não retornar a resposta diretamente.
  • Plugins do Fastify: Se um FastifyPluginAsync não usar await, remova a palavra-chave async e retorne Promise.resolve() para manter a conformidade com require-await enquanto preserva a integridade dos tipos.
  • Tratamento de Erros: Toda operação com await deve estar dentro de um bloco try/catch ou 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 PascalCase para classes e camelCase para 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 do useEffect. Prefira Server Actions (se aplicável) ou propriedades action otimizadas 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 void para chamadas não aguardadas.
  • Refs: Acessar ref.current durante 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 de 0 ou NaN na tela.
  • Sincronização de Estado: Evite chamar setState sincronamente dentro do useEffect se 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. Use console.info/warn/error com 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 em px é estritamente proibido para dimensionamento, espaçamento e tipografia.
    • Exceção: 1px é permitido para bordas quando se deseja um efeito de linha fina (hairline).
  • Tipografia Relativa: Sempre use rem para tamanhos de fonte. Nunca use px ou em para tipografia.
  • Espaçamento Fluido: Use rem para espaçamento consistente ou clamp() para espaçamento fluido que se adapta ao viewport.
  • Layouts Estruturais: Use primitivos de layout modernos: flexbox, grid, %, vw e vh.
  • Funções Lógicas: Aproveite clamp(), min() e max() para criar limites para elementos fluidos sem usar media queries para cada pequeno ajuste.

Exemplos Responsivos

ResponsiveHero.module.css
.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);
}
ResponsiveHero.tsx
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 camelCase para 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)

Uso Restrito

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:

  1. Componentes React: Seguindo padrões rigorosos (booleanos explícitos, tipos estritos, etc.).
  2. 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.

AdvancedCycleManager.module.css
.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;
}
Editor em tempo real
/**
 * 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>
  );
}
Resultado
Loading...

Última Atualização: Junho de 2026