Adicionada compressão Brotli/Gzip, auto-import de Vue e PrimeVue, e análise visual do bundle para otimização de produção e Remove AppLayout duplicado de cada área (therapist, admin, configuracoes, account, supervisor, billing, features) e consolida sob um único pai no router/index.js. Adiciona RouterPassthrough para grupos de rota sem layout intermediário. Remove debug ativo (console.trace em router.push e queries Supabase em todo watch de rota) que degradava performance para todos os usuários.
This commit is contained in:
194
docs/architecture/router-shared-applayout.md
Normal file
194
docs/architecture/router-shared-applayout.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Router — AppLayout Compartilhado
|
||||
|
||||
**Data:** 2026-03-25
|
||||
**Arquivo:** `src/router/index.js` e arquivos de rota relacionados
|
||||
|
||||
---
|
||||
|
||||
## Problema
|
||||
|
||||
A aplicação apresentava comportamento de "reload" ao navegar entre seções distintas (ex: de `/configuracoes/agenda` para `/therapist/agenda/recorrencias`). O efeito visual era a sidebar e a topbar piscando e sendo completamente reconstruídas — mesmo sendo o mesmo layout.
|
||||
|
||||
Além disso, ao clicar rapidamente para trocar de página, o Vue recarregava o componente de layout inteiro, causando lentidão perceptível.
|
||||
|
||||
### Causa raiz
|
||||
|
||||
Cada área da aplicação tinha seu **próprio registro de rota com `component: AppLayout`**:
|
||||
|
||||
```
|
||||
/therapist → { component: AppLayout } ← instância A
|
||||
/admin → { component: AppLayout } ← instância B
|
||||
/configuracoes→ { component: AppLayout } ← instância C
|
||||
/account → { component: AppLayout } ← instância D
|
||||
...
|
||||
```
|
||||
|
||||
O Vue Router trata registros de rota distintos como componentes distintos. Ao navegar de `/configuracoes` para `/therapist`, o Vue **desmontava** a instância A e **montava** a instância B do zero — sidebar, topbar, watchers, stores, tudo reiniciava. Isso é o comportamento correto do Vue Router, mas estava sendo usado de forma incorreta para uma aplicação que tem um layout único.
|
||||
|
||||
---
|
||||
|
||||
## Solução
|
||||
|
||||
Consolidar todas as áreas autenticadas sob **um único registro de rota pai** com `component: AppLayout`. O Vue Router reutiliza a instância do componente pai enquanto navega entre os filhos.
|
||||
|
||||
### Arquitetura após a mudança
|
||||
|
||||
```
|
||||
/ → AppLayout (instância única — nunca desmontada)
|
||||
├── therapist/ → RouterPassthrough → página
|
||||
├── admin/ → RouterPassthrough → página
|
||||
├── configuracoes/ → ConfiguracoesPage → sub-página
|
||||
├── account/ → RouterPassthrough → página
|
||||
├── upgrade/ → RouterPassthrough → página
|
||||
├── supervisor/ → RouterPassthrough → página
|
||||
└── features/ → RouterPassthrough → página
|
||||
```
|
||||
|
||||
**Antes:** AppLayout era desmontado/remontado ao trocar de seção.
|
||||
**Depois:** Apenas o conteúdo (área central) é trocado. AppLayout nunca desmonta.
|
||||
|
||||
---
|
||||
|
||||
## Componente RouterPassthrough
|
||||
|
||||
Criado em `src/layout/RouterPassthrough.vue`:
|
||||
|
||||
```vue
|
||||
<template><router-view /></template>
|
||||
```
|
||||
|
||||
Serve como "rota de grupo com componente" para as áreas que não têm layout intermediário próprio (therapist, admin, account etc.). Ele é necessário porque o Vue Router precisa de um componente em cada nível da hierarquia para renderizar os filhos via `<router-view>`.
|
||||
|
||||
---
|
||||
|
||||
## Arquivos alterados
|
||||
|
||||
### `src/router/index.js`
|
||||
|
||||
Antes: importava cada rota e espalhava todas no nível raiz do array de rotas.
|
||||
Depois: cria um único pai `{ path: '/', component: AppLayout }` e coloca todas as rotas de app como filhas.
|
||||
|
||||
```js
|
||||
const routes = [
|
||||
// Rotas sem layout
|
||||
...publicRoutes,
|
||||
...authRoutes,
|
||||
|
||||
// Setup wizards (fullscreen, fora do AppLayout)
|
||||
...therapistStandalone,
|
||||
...clinicStandalone,
|
||||
|
||||
// ─── AppLayout compartilhado ───────────────────────────
|
||||
{
|
||||
path: '/',
|
||||
component: AppLayout,
|
||||
children: [
|
||||
therapistRoutes, // path: 'therapist'
|
||||
adminRoutes, // path: 'admin'
|
||||
accountRoutes, // path: 'account'
|
||||
billingRoutes, // path: 'upgrade'
|
||||
supervisorRoutes, // path: 'supervisor'
|
||||
featuresRoutes, // path: 'features'
|
||||
configuracoesRoutes, // path: 'configuracoes'
|
||||
]
|
||||
},
|
||||
|
||||
// AppLayout próprio (usuários completamente distintos)
|
||||
saasRoutes,
|
||||
editorRoutes,
|
||||
portalRoutes,
|
||||
|
||||
// Catch-all (sempre por último)
|
||||
...miscRoutes,
|
||||
];
|
||||
```
|
||||
|
||||
### `src/router/routes.therapist.js` e `routes.clinic.js`
|
||||
|
||||
Setup wizards (tela cheia, sem layout) foram separados como `export const therapistStandalone` / `clinicStandalone` e continuam com paths absolutos (`/therapist/setup`, `/admin/setup`).
|
||||
|
||||
A rota principal passou de:
|
||||
```js
|
||||
{ path: '/therapist', component: AppLayout, children: [...] }
|
||||
```
|
||||
Para:
|
||||
```js
|
||||
{ path: 'therapist', component: RouterPassthrough, meta: {...}, children: [...] }
|
||||
```
|
||||
|
||||
### `src/router/routes.configs.js`
|
||||
|
||||
Removido o double-nesting desnecessário. Antes havia:
|
||||
```js
|
||||
{ path: '/configuracoes', component: AppLayout,
|
||||
children: [
|
||||
{ path: '', component: ConfiguracoesPage, children: [...] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Depois, `ConfiguracoesPage` (que já tem `<router-view>` próprio) assume diretamente:
|
||||
```js
|
||||
{ path: 'configuracoes', component: ConfiguracoesPage,
|
||||
redirect: { name: 'ConfiguracoesAgenda' },
|
||||
meta: { requiresAuth: true, roles: [...] },
|
||||
children: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### `src/router/guards.js`
|
||||
|
||||
`/configuracoes` adicionado ao `isTenantArea` para garantir que o tenant e entitlements são carregados corretamente ao acessar a URL diretamente:
|
||||
|
||||
```js
|
||||
const isTenantArea =
|
||||
to.path.startsWith('/admin') ||
|
||||
to.path.startsWith('/therapist') ||
|
||||
to.path.startsWith('/supervisor') ||
|
||||
to.path.startsWith('/configuracoes');
|
||||
```
|
||||
|
||||
### `src/App.vue`
|
||||
|
||||
Mesma correção no `isTenantArea` local, que controla o `checkSetupWizard`.
|
||||
|
||||
---
|
||||
|
||||
## O que foi mantido separado (e por quê)
|
||||
|
||||
| Área | Motivo para manter AppLayout próprio |
|
||||
|---|---|
|
||||
| `/saas` | Usado exclusivamente por `saas_admin`. Nunca há navegação cruzada com áreas de tenant. |
|
||||
| `/editor` | Role de plataforma distinto. Área separada. |
|
||||
| `/portal` | Sessão de paciente. Usuário completamente diferente. |
|
||||
|
||||
Mesclar essas áreas no AppLayout compartilhado não traria ganho algum e criaria acoplamento desnecessário entre contextos de usuário distintos.
|
||||
|
||||
---
|
||||
|
||||
## Debug removido na mesma sessão
|
||||
|
||||
Foram removidos resquícios de código de debug que estavam ativos em produção e impactavam **todos** os usuários:
|
||||
|
||||
- **`router/index.js`**: `console.trace()` injetado em `router.push` e `router.replace` a cada clique de navegação (gerava stack trace completo).
|
||||
- **`App.vue`**: `debugSnapshot()` disparava 3 queries Supabase (`auth.getUser`, `profiles`, `rpc.my_tenants`) a **cada troca de rota**, causando o principal sintoma de lentidão.
|
||||
|
||||
O sistema de suporte técnico (`supportGuard` + `supportDebugStore`) **não foi afetado** — é uma feature controlada que só ativa via `?support=TOKEN` validado no banco.
|
||||
|
||||
---
|
||||
|
||||
## Meta inheritance
|
||||
|
||||
O Vue Router 4 mescla automaticamente o `meta` de todos os registros de rota correspondidos (`to.matched`). Isso significa que os filhos do `RouterPassthrough` herdam o `meta` do pai sem nenhuma configuração adicional.
|
||||
|
||||
Exemplo: ao navegar para `/therapist/agenda`, `to.meta` conterá:
|
||||
```js
|
||||
{
|
||||
area: 'therapist', // herdado do pai 'therapist'
|
||||
requiresAuth: true, // herdado do pai 'therapist'
|
||||
roles: ['therapist'], // herdado do pai 'therapist'
|
||||
feature: 'agenda.view' // da própria rota 'agenda'
|
||||
}
|
||||
```
|
||||
|
||||
Os guards continuam funcionando sem nenhuma mudança de lógica.
|
||||
Reference in New Issue
Block a user