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: @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. O hook genérico useValidatedForm 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, 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