refactor(routes): quebra App.tsx (336L→67L) em src/routes/ por área (F1-1.x)#119
Conversation
…F1-1.x) ## Onda B do plano de auditoria F1 Refatora `src/App.tsx` que tinha **336 linhas, 26 imports e 96 rotas em um único arquivo** — bagunça crítica identificada na auditoria como item #6 do Top 20. ## Mudanças | Antes | Depois | |---|---| | `src/App.tsx` 336 linhas | `src/App.tsx` **67 linhas (-80%)** | | 96 rotas em 1 arquivo | 96 rotas em 9 arquivos por área | | 26 imports no topo | 22 imports (só providers e wrappers) | | RoutePrefetcher inline | `src/routes/RoutePrefetcher.tsx` | | RouteSuspense inline | `src/routes/AppRoutes.tsx` (extraído) | ## Estrutura nova ``` src/App.tsx 67L ← só providers e estrutura src/routes/ ├── AppRoutes.tsx 49L ← orquestrador <Routes> ├── lazy-pages.ts 129L ← todos lazyWithRetry centralizados ├── RoutePrefetcher.tsx 41L ← helper de prefetch ├── public-routes.tsx 17L ← /login, /reset, /unauthorized (4) ├── product-routes.tsx 35L ← produtos, filtros, novidades (12) ├── quote-routes.tsx 28L ← orçamentos (8) ├── admin-routes.tsx 126L ← /admin/* + DevRoute (33) ├── tools-routes.tsx 45L ← simulador, mockup, BI (17) └── client-routes.tsx 50L ← home, clientes, redirects, 404 (12) ``` ## Validação de equivalência - ✅ **93 paths únicos antigos = 93 paths únicos novos** (diff vazio) - ✅ Ordem de matching preservada (`homeAndClientRoutes` mantém o catch-all `*` mounted last) - ✅ `<AdminRoute />` e `<DevRoute />` permanecem aninhados corretamente - ✅ `<DeprecatedRoute />` mantida nos 4 paths legados - ✅ Todos os `Navigate replace` preservados - ✅ `@/pages/...` em vez de `./pages/...` para path absoluto consistente ## Arquivos NÃO mexidos - `src/pages/**/*` — nenhum page foi movido. **Baseline TS intacto.** - `src/components/layout/{Protected,Admin,Dev,Deprecated}Route.tsx` — guards permanecem como antes. - `src/lib/lazyWithRetry.ts` — helper preservado. ## Por que importa Antes, mexer numa rota exigia rolar até linha ~250 do App.tsx num arquivo gigante com 7 Providers aninhados. Agora cada área é um arquivo de até 130 linhas com escopo claro. Adicionar uma rota nova passa a ser: 1. Adicionar entry em `lazy-pages.ts` 2. Adicionar `<Route>` no arquivo da área correspondente Sem mais tocar o App.tsx, sem mais conflitos de merge no arquivo central quando 2 PRs adicionam rotas em áreas diferentes. ## Risk 🟡 **Médio**. Reorganização estrutural extensa, mas: - Zero mudança de comportamento (paths idênticos, ordem preservada) - Sem tocar nenhum page component - Equivalência verificada via diff de paths ## Test plan - [x] 93 paths únicos antigos = 93 novos (diff /tmp/old vs /tmp/new vazio) - [x] App.tsx 336L → 67L - [x] Todos os 9 arquivos novos com header de propósito documentado - [ ] CI verde (tsc, ESLint, build, smoke tests, ref-warning gates) - [ ] CodeRabbit OK - [ ] Vercel preview funciona (todas as rotas navegáveis)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughRefactor de roteamento que extrai ~279 linhas de definições inline de ChangesConsolidação do Sistema de Roteamento
Sequence Diagram(s)sequenceDiagram
participant Client
participant Router
participant RoutePrefetcher
participant Suspense
participant ChunkLoader
Client->>Router: navigate(pathname)
Router->>RoutePrefetcher: notify pathname
RoutePrefetcher->>ChunkLoader: import() (immediate or delayed)
Router->>Suspense: render route (fallback = getFallback(pathname))
Suspense->>ChunkLoader: request chunk if not loaded
ChunkLoader-->>Suspense: deliver chunk
Suspense-->>Client: render page
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/routes/RoutePrefetcher.tsx`:
- Around line 17-35: The dynamic import() calls in RoutePrefetcher.tsx (the
fire-and-forget prefetches inside the pathname branches and the setTimeout
block) are unhandled and can cause unhandled promise rejections; add a small
helper (e.g., safePrefetch or consumeImport) that invokes import(...).catch(()
=> {/* swallow/log */}) and replace each raw import("@/...") call in the
branches and the timeout with safePrefetch(...) so failures are absorbed
(optionally log at debug level) while keeping the best-effort prefetch behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 815109cc-b0e8-4588-8e84-66bbe00c0602
📒 Files selected for processing (10)
src/App.tsxsrc/routes/AppRoutes.tsxsrc/routes/RoutePrefetcher.tsxsrc/routes/admin-routes.tsxsrc/routes/client-routes.tsxsrc/routes/lazy-pages.tssrc/routes/product-routes.tsxsrc/routes/public-routes.tsxsrc/routes/quote-routes.tsxsrc/routes/tools-routes.tsx
| import("@/pages/Index"); | ||
| import("@/pages/FiltersPage"); | ||
| } else if (pathname === "/") { | ||
| // On dashboard, prefetch products and quotes | ||
| import("@/pages/FiltersPage"); | ||
| import("@/pages/QuotesListPage"); | ||
| import("@/pages/ClientsPage"); | ||
| } else if (pathname === "/produtos") { | ||
| // On products page, prefetch detailed product view and tools | ||
| import("@/pages/ProductDetail"); | ||
| import("@/pages/MockupGenerator"); | ||
| import("@/pages/PriceSimulatorPage"); | ||
| } | ||
|
|
||
| // Low priority prefetch for common tools | ||
| const timeoutId = setTimeout(() => { | ||
| if (pathname !== "/orcamentos/novo") import("@/pages/QuoteBuilderPage"); | ||
| if (pathname !== "/novidades") import("@/pages/NoveltiesPage"); | ||
| }, 2000); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verifica imports dinâmicos sem tratamento de erro no prefetcher.
set -euo pipefail
echo "== Dynamic import sem catch =="
rg -nP --type=tsx 'import\(.+\)\s*;?$' src/routes/RoutePrefetcher.tsx
echo
echo "== Dynamic import com catch =="
rg -nP --type=tsx 'import\(.+\)\.catch\(' src/routes/RoutePrefetcher.tsx || true
echo
echo "Esperado: após o ajuste, os imports de prefetch devem passar por helper com .catch()."Repository: adm01-debug/Promo_Gifts
Length of output: 144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# List files in src/routes/
echo "== Files in src/routes/ =="
if [ -d "src/routes" ]; then
ls -la src/routes/ || true
else
echo "Directory src/routes/ does not exist"
fi
echo
echo "== Search for RoutePrefetcher in repo =="
fd -i routeprefetcher . 2>/dev/null || trueRepository: adm01-debug/Promo_Gifts
Length of output: 898
🏁 Script executed:
cat -n src/routes/RoutePrefetcher.tsxRepository: adm01-debug/Promo_Gifts
Length of output: 1777
Trate rejeições dos import() de prefetch para evitar unhandled rejection.
Os import() nas linhas 17-18, 21-23, 26-28 e 33-34 são fire-and-forget sem .catch(). Em falha de chunk ou rede, isso gera rejeição não tratada no console. Embora prefetch seja best-effort, precisa absorver explicitamente os erros.
Solução prática: envolver com helper que consome o erro:
+const prefetch = (loader: () => Promise<unknown>) => {
+ void loader().catch(() => {
+ // prefetch é best-effort, falhas são silenciosas
+ });
+};
import("@/pages/Index");
-import("@/pages/FiltersPage");
+prefetch(() => import("@/pages/FiltersPage"));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/routes/RoutePrefetcher.tsx` around lines 17 - 35, The dynamic import()
calls in RoutePrefetcher.tsx (the fire-and-forget prefetches inside the pathname
branches and the setTimeout block) are unhandled and can cause unhandled promise
rejections; add a small helper (e.g., safePrefetch or consumeImport) that
invokes import(...).catch(() => {/* swallow/log */}) and replace each raw
import("@/...") call in the branches and the timeout with safePrefetch(...) so
failures are absorbed (optionally log at debug level) while keeping the
best-effort prefetch behavior.
PR #119 quebrou o App.tsx em arquivos por área em src/routes/, mas o `scripts/smoke-tests.mjs` ainda lia APENAS `src/App.tsx` procurando strings 'path="..."'. Como os paths foram movidos pra src/routes/*.tsx, o gate quebrou nas 7 rotas críticas: /login, /reset-password, /auth/callback, /produtos, /orcamentos, /orcamentos/novo, /admin/usuarios Patch: `checkStaticRoutes()` agora lê App.tsx + todos os .tsx/.ts em src/routes/, concatena, e procura os paths em todos eles. Validado localmente: ✓ static-routes: 7 rotas críticas declaradas Risk: 🟢 zero. Smoke test apenas.
There was a problem hiding this comment.
Pull request overview
Refatora a árvore de rotas do app, extraindo o conteúdo antes concentrado em src/App.tsx para módulos em src/routes/ por “área”, mantendo App.tsx focado em providers/wrappers e delegando o <Routes> para um orquestrador (AppRoutes).
Changes:
- Extrai
<Routes>+RouteSuspenseparasrc/routes/AppRoutes.tsxe reduzsrc/App.tsxpara composição de providers e estrutura. - Centraliza imports lazy (via
lazyWithRetry) emsrc/routes/lazy-pages.tse distribui as rotas em arquivos por domínio (public/product/quote/admin/tools/client). - Extrai
RoutePrefetcherparasrc/routes/RoutePrefetcher.tsx.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/App.tsx |
Remove definição inline de rotas/lazy imports e passa a renderizar AppRoutes + RoutePrefetcher sob os providers existentes. |
src/routes/AppRoutes.tsx |
Orquestra a árvore de rotas (public + ProtectedRoute com subgrupos) e aplica Suspense com getFallback(pathname). |
src/routes/RoutePrefetcher.tsx |
Implementa prefetch de chunks por pathname e prefetch de baixa prioridade via setTimeout. |
src/routes/lazy-pages.ts |
Centraliza todos os lazyWithRetry(() => import(...)) para pages por área. |
src/routes/public-routes.tsx |
Define rotas públicas (login/reset/callback/unauthorized). |
src/routes/product-routes.tsx |
Define rotas de produtos (lista/detalhe/filtros/etc) e redirects legados. |
src/routes/quote-routes.tsx |
Define rotas de orçamentos (lista/dashboard/kanban/templates/builder/view). |
src/routes/admin-routes.tsx |
Define rotas admin sob AdminRoute, com subset dev-only sob DevRoute, e rotas deprecated. |
src/routes/tools-routes.tsx |
Define rotas de ferramentas (simuladores, BI, mockup, kits, dropbox, etc) + redirects. |
src/routes/client-routes.tsx |
Define home/dashboard/CRM + redirects legados e catch-all * (404). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| useEffect(() => { | ||
| // Only prefetch if we're not on a mobile connection or low power mode if detectable | ||
| if (pathname === "/login") { | ||
| // Prefetch index/produtos right after login screen loads | ||
| import("@/pages/Index"); | ||
| import("@/pages/FiltersPage"); | ||
| } else if (pathname === "/") { | ||
| // On dashboard, prefetch products and quotes | ||
| import("@/pages/FiltersPage"); | ||
| import("@/pages/QuotesListPage"); | ||
| import("@/pages/ClientsPage"); | ||
| } else if (pathname === "/produtos") { | ||
| // On products page, prefetch detailed product view and tools | ||
| import("@/pages/ProductDetail"); | ||
| import("@/pages/MockupGenerator"); | ||
| import("@/pages/PriceSimulatorPage"); | ||
| } | ||
|
|
||
| // Low priority prefetch for common tools | ||
| const timeoutId = setTimeout(() => { | ||
| if (pathname !== "/orcamentos/novo") import("@/pages/QuoteBuilderPage"); | ||
| if (pathname !== "/novidades") import("@/pages/NoveltiesPage"); | ||
| }, 2000); |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/smoke-tests.mjs (1)
77-82: ⚡ Quick winVarredura de
src/routesestá só no nível raizHoje o script ignora subpastas em
src/routes. Se a organização evoluir para diretórios por domínio, esse check pode falhar com falso negativo e bloquear CI sem regressão real.💡 Diff sugerido (coleta recursiva)
- if (existsSync(routesDir)) { - for (const name of readdirSync(routesDir)) { - if (name.endsWith(".tsx") || name.endsWith(".ts")) { - candidates.push(resolve(routesDir, name)); - } - } - } + const collectRouteFiles = (dir) => { + const files = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const fullPath = resolve(dir, entry.name); + if (entry.isDirectory()) { + files.push(...collectRouteFiles(fullPath)); + } else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) { + files.push(fullPath); + } + } + return files; + }; + + if (existsSync(routesDir)) { + candidates.push(...collectRouteFiles(routesDir)); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/smoke-tests.mjs` around lines 77 - 82, The loop that reads routesDir using readdirSync only inspects the top-level entries (the block iterating over readdirSync(routesDir) and pushing into candidates when name.endsWith(".tsx") or ".ts") and thus ignores nested subdirectories; change this to a recursive directory walker (e.g., a helper like collectRouteFiles or inline recursion) that traverses subfolders under routesDir, checks Dirent/file types (readdirSync with { withFileTypes: true } or statSync) and pushes any .ts/.tsx files into the candidates array so that nested route files are discovered properly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/smoke-tests.mjs`:
- Line 89: The current route-checking logic uses src.includes(`path="${r}"`)
which is brittle and misses valid variants (single quotes, spaces). Replace the
naive includes check used where REQUIRED_ROUTES is filtered (the const missing =
... expression using src.includes) with a regex-based match that looks for
path\s*=\s*(['"`]){route}\1 or otherwise matches path attribute with single or
double quotes and optional spaces; implement this by constructing a RegExp from
each route (escape special chars) and testing src.match or RegExp.test so the
missing calculation correctly detects routes regardless of quoting/spacing.
---
Nitpick comments:
In `@scripts/smoke-tests.mjs`:
- Around line 77-82: The loop that reads routesDir using readdirSync only
inspects the top-level entries (the block iterating over readdirSync(routesDir)
and pushing into candidates when name.endsWith(".tsx") or ".ts") and thus
ignores nested subdirectories; change this to a recursive directory walker
(e.g., a helper like collectRouteFiles or inline recursion) that traverses
subfolders under routesDir, checks Dirent/file types (readdirSync with {
withFileTypes: true } or statSync) and pushes any .ts/.tsx files into the
candidates array so that nested route files are discovered properly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a99ec626-19bf-42bf-86ad-d9afb551b8a3
📒 Files selected for processing (1)
scripts/smoke-tests.mjs
| } | ||
| const src = readFileSync(appPath, "utf8"); | ||
| const src = existing.map((p) => readFileSync(p, "utf8")).join("\n"); | ||
| const missing = REQUIRED_ROUTES.filter((r) => !src.includes(`path="${r}"`)); |
There was a problem hiding this comment.
Match de rota está frágil e pode gerar falso negativo
includes(\path="${r}"`)só cobre formato exato. Qualquer variação válida (aspas simples ou espaços em volta de=`) quebra o check estático.
💡 Diff sugerido (regex mais resiliente)
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- const missing = REQUIRED_ROUTES.filter((r) => !src.includes(`path="${r}"`));
+ const missing = REQUIRED_ROUTES.filter((r) => {
+ const pattern = new RegExp(`path\\s*=\\s*["']${escapeRegExp(r)}["']`);
+ return !pattern.test(src);
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const missing = REQUIRED_ROUTES.filter((r) => !src.includes(`path="${r}"`)); | |
| const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| const missing = REQUIRED_ROUTES.filter((r) => { | |
| const pattern = new RegExp(`path\\s*=\\s*["']${escapeRegExp(r)}["']`); | |
| return !pattern.test(src); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/smoke-tests.mjs` at line 89, The current route-checking logic uses
src.includes(`path="${r}"`) which is brittle and misses valid variants (single
quotes, spaces). Replace the naive includes check used where REQUIRED_ROUTES is
filtered (the const missing = ... expression using src.includes) with a
regex-based match that looks for path\s*=\s*(['"`]){route}\1 or otherwise
matches path attribute with single or double quotes and optional spaces;
implement this by constructing a RegExp from each route (escape special chars)
and testing src.match or RegExp.test so the missing calculation correctly
detects routes regardless of quoting/spacing.
Onda B do plano de auditoria F1
Refatora
src/App.tsxque tinha 336 linhas, 26 imports e 96 rotas em um único arquivo — bagunça crítica identificada na auditoria como item #6 do Top 20.📦 Mudanças
src/App.tsx336 linhassrc/App.tsx67 linhas (-80%)src/routes/RoutePrefetcher.tsxsrc/routes/AppRoutes.tsx(extraído)🗂 Estrutura nova
✅ Validação de equivalência
diff /tmp/old-paths /tmp/new-paths= vazio)homeAndClientRoutesmantém o catch-all*mounted last)<AdminRoute />e<DevRoute />permanecem aninhados corretamente<DeprecatedRoute />mantida nos 4 paths legados (performance, performance-comercial, comissoes 2x)Navigate replacepreservados@/pages/...em vez de./pages/...para path absoluto consistente (resolve via tsconfig)🔒 Arquivos NÃO mexidos
src/pages/**/*— nenhum page foi movido. Baseline TS intacto.src/components/layout/{Protected,Admin,Dev,Deprecated}Route.tsx— guards permanecem como antes.src/lib/lazyWithRetry.ts— helper preservado.src/App.css— estilos preservados.💡 Por que importa
Antes, mexer numa rota exigia rolar até linha ~250 do App.tsx num arquivo gigante com 7 Providers aninhados. Agora cada área é um arquivo de até 130 linhas com escopo claro.
Adicionar uma rota nova passa a ser:
lazy-pages.ts<Route>no arquivo da área correspondenteSem mais tocar o App.tsx, sem mais conflitos de merge no arquivo central quando 2 PRs adicionam rotas em áreas diferentes.
🟡 Médio. Reorganização estrutural extensa, mas:
📋 Test plan
Summary by CodeRabbit
Refactor
Novos Recursos
Chore