01 — Stack e decisões

01 — Stack e decisões

Define a stack fixa do projeto web e o que cada peça resolve. Itens marcados FIXO não são escolha do projeto novo — mudá-los quebra compatibilidade com o time. Itens NEGOCIÁVEL podem variar se houver motivo declarado. Versões verificadas em 2026-08-19 contra o npm registry e testadas de verdade (tsc --noEmit, lint, build, tela no browser) — não é combinação só especulada.

Stack

Peça Versão Status Por quê
Node >= 24 FIXO Última LTS ativa (Node 24, "Krypton"). process.loadEnvFile exige ≥ 20.6
TypeScript ^6.0 FIXO Bumpado de 5.9. Teto real: typescript-eslint (v8, latest) exige typescript >=4.8.4 <6.1.0 — 6.0.3 é a versão mais alta possível hoje. 7.x fica pra quando houver suporte
Vite ^8.2 FIXO Bumpado de 4. @vitejs/plugin-react v6 exige vite ^8 — os três (vite, @vitejs/plugin-react, vite-plugin-checker) sobem juntos
React ^19.2 FIXO Bumpado de 18. @curio/client só exige react >=16.8.0 — sem bloqueio
@curio/client ^1.4.2 FIXO Transporte proprietário para o backend. Não avaliado nesta rodada (decisão do projeto)
MUI (@mui/material) ^9.3 FIXO Bumpado de 5 direto para 9 — ver migração MUI 5→9
@mui/x-date-pickers ^9.11 FIXO Acoplado ao major do @mui/material. Peer cobre React 19
@tanstack/react-query ^5.101 FIXO Bumpado de 4.36. cacheTime→gcTime já ajustado nos hooks
react-router-dom ^7.18 FIXO Bumpado de 6. BrowserRouter não aceita mais a prop future
react-hook-form ^7.85 FIXO Bumpado de 7.48 — exigido pelo @hookform/resolvers v5 (ver Zod abaixo)
@hookform/resolvers ^5.9 FIXO Bumpado de 3 — obrigatório para usar Zod v4, não é opcional
zod ^4.4 FIXO Bumpado de 3. Mudança de tipos exigiu ajuste em useValidatedForm — ver abaixo
date-fns ^4.4 FIXO Bumpado de 2. Exige @mui/x-date-pickers/AdapterDateFns (v9 já é o adapter para v3/v4 — ver nota)
ESLint 10 + Prettier 3 — FIXO Bumpado de 8. Flat config (eslint.config.js, não mais .eslintrc.json) — ver 14
typescript-eslint ^8.67 FIXO Bumpado de 7. Compatível com ESLint 8/9/10 e TS <6.1.0 — é o teto que bloqueia o TypeScript acima
eslint-plugin-react-hooks ^7.1 FIXO Bumpado de 4. recommended ficou bem mais amplo (regras novas tipo set-state-in-effect) — ver 14
husky + lint-staged — FIXO Gate de pré-commit. lint-staged bumpado de 15 para 17 (dev tool, sem mudança de config)
Emotion ^11.14 FIXO Peer dependency do MUI

Sem suíte de testes por padrão. Este guia não inventa uma nem assume que ela existe. O gate de qualidade é type-check + lint + build — ver 14 e 19. Se o projeto novo quiser testes, essa é uma decisão a tomar explicitamente no início, não depois.

Por que nem tudo foi para o latest

Em 2026-08-19, depois de uma rodada completa de atualização testada de verdade, só sobrou um pacote fora do latest:

Pacote Aqui Latest hoje Ficou de fora porque
TypeScript 6.0.3 7.0.2 typescript-eslint@8.67.0 (latest estável) exige typescript >=4.8.4 <6.1.0 — não há versão estável do typescript-eslint que aceite TS 7 ainda (só canary). 6.0.3 é o teto real: existe uma linha 6.x entre o 5.9 antigo e o 7.0 novo, e ela cabe no range aceito — não pule direto de 5→7 sem checar se há uma minor intermediária. Bumpar além de 6.0.x quebraria o lint, não é escolha.

Todo o resto (MUI, Vite, ESLint, date-fns, typescript-eslint, react-hooks) já está no latest — ver as notas de migração abaixo para quem for repetir isso num projeto mais antigo.

Migração MUI 5 → 9: o que mudou de verdade

O caminho não é incremental por major (5→6→7→8→9): @mui/x-date-pickers@9 exige @mui/material: "^7.3.0 || ^9.0.0" — não aceita a v8 como peer. Suba os três pacotes (@mui/material, @mui/icons-material, @mui/x-date-pickers) juntos, direto pra v9.

Superfície de quebra real, testada com tsc --noEmit:

  • Sem uso de Grid no projeto — a maior fonte de quebra entre majors do MUI não se aplicou aqui. Se o projeto novo usa Grid, verifique a API v1→v2 separadamente.
  • InputProps, inputProps, InputLabelProps foram removidos de TextField/Checkbox (não são só deprecados — o tipo não aceita mais). Viram slotProps: InputProps→slotProps.input, inputProps→slotProps.htmlInput, InputLabelProps→slotProps.inputLabel. Isso afeta qualquer componente de common/ que componha TextField (FormField, CurrencyInput, PhoneInput, CnpjInput, DatePickerInput, DataTable, TransferListCard).
  • Props de atalho de estilo direto em Box/Stack/Typography/DialogTitle (display, alignItems, justifyContent, fontWeight, etc., sem passar por sx) pararam de tipar — MUI removeu o suporte a "system props" desses componentes. Mova pra sx={{ ... }}.
  • disableEscapeKeyDown foi removido de Dialog/Modal sem substituto direto — se o Dialog não tem onClose, a prop já era redundante (nada fecha no Escape de qualquer forma); se tem, trate a reason dentro do próprio onClose.
  • @mui/x-date-pickers inverteu a convenção de adapter entre majors: na v6/v7, AdapterDateFns era para date-fns v2 e AdapterDateFnsV3 para v3/v4. Na v9, inverteu — AdapterDateFns (sem sufixo) já é o adapter para v3/v4, e AdapterDateFnsV2 é o legado. Confira o import de novo a cada major, não assuma que o nome antigo continua correto.

date-fns e o adapter do x-date-pickers

Bumpar date-fns sem trocar o subpath do adapter (ver acima) quebra silenciosamente em runtime, não em tipo — o import resolve, mas o parsing de data se comporta diferente. Sempre os dois juntos.

Decisões que valem entender

O backend não é REST

@curio/client é um transporte RPC proprietário. Não existe GET /api/fornecedores. Existe: abrir um caso de uso por id numérico e enviar requests nomeados (RM_OBTEM_LISTA) dentro dele.

Consequência prática: não use axios, fetch direto (fora do carregamento do config.json), nem qualquer geração de cliente a partir de OpenAPI. Ver 06.

O fetch do config é a única exceção

src/lib/curio/index.ts faz fetch("./config.json") para descobrir a URL do serviço em runtime. Isso é intencional: permite trocar de ambiente sem rebuild. Ver 04.

React Query v5 — gcTime, não cacheTime

A opção cacheTime foi renomeada para gcTime na v5, em defaultOptions.queries e defaultOptions.mutations do queryClient.ts, e em qualquer useQuery que a declare (useConnectQuery). isPending já era o nome correto desde a v4.36 — nenhuma mudança ali.

Zod v4 exige @hookform/resolvers v5 e um ajuste de generics

Zod v4 mudou a arquitetura interna de tipos de ZodType. Duas consequências reais, encontradas rodando tsc de verdade, não hipotéticas:

  1. @hookform/resolvers v3 só aceita Zod ^3 — subir o Zod exige subir os resolvers para v5 (que por sua vez exige react-hook-form >= 7.55.0). Não é uma escolha independente.
  2. O hook genérico useValidatedForm<T extends z.ZodType> deixou de compilar: o output de z.ZodType em v4 não fecha automaticamente em FieldValues. A correção é dupla — apertar o bound do genérico para z.ZodType<FieldValues>, e no ponto de chamada do zodResolver, usar um cast de tipo (as any justificado + as unknown as Resolver<...>) porque o tipo interno Zod4Type do resolver não compõe com um schema genérico sem perder a inferência. Ver src/hooks/useValidatedForm.ts — o cast é só de tipo; zodResolver continua validando em runtime exatamente como antes.

React Router v7 — sem prop future

Os future flags (v7_startTransition, v7_relativeSplatPath) que o BrowserRouter aceitava na v6 viraram comportamento padrão na v7. A prop future não existe mais — remova-a de main.tsx.

Estado de servidor no React Query, estado de UI no componente

Não há Redux, Zustand ou store global. O que vem do backend vive no React Query; o resto é useState local ou Context (AuthProvider, NotificationProvider, TabsContext). Se você sentir falta de uma store global, provavelmente está guardando resposta de servidor no lugar errado.

Erro é global, não local

Nenhuma página trata erro de request com try/catch para exibir mensagem. O QueryCache/MutationCache do queryClient captura e empurra para o NotificationProvider. Ver 07.

Negociável

Item Padrão sugerido Quando mudar
Porta do dev server 5000 Conflito local; ajuste em vite.config.ts
Paleta do tema Azul #1976d2 Identidade visual do produto novo
Navegação por abas Opcional Só adote se o produto realmente precisar de abas
Dark mode Ausente Se o produto exigir; ver 12
sourcemap em produção true Desligue se o bundle for público e sensível

Revision #2
Created Thu, Aug 20, 2026 4:43 PM by Geraldo Barbosa
Updated Tue, Aug 25, 2026 4:49 PM by Geraldo Barbosa