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