Files
agenciapsilmno/docs/architecture/router-shared-applayout.md

195 lines
6.9 KiB
Markdown

# 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.