diff --git a/docs/architecture/router-shared-applayout.md b/docs/architecture/router-shared-applayout.md
new file mode 100644
index 0000000..db21505
--- /dev/null
+++ b/docs/architecture/router-shared-applayout.md
@@ -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
+
+```
+
+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.
diff --git a/package-lock.json b/package-lock.json
index b59f100..0a88ddf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -45,9 +45,10 @@
"rollup-plugin-visualizer": "^7.0.1",
"sass": "^1.55.0",
"tailwindcss": "^4.1.18",
- "unplugin-auto-import": "^21.0.0",
+ "unplugin-auto-import": "^0.18.6",
"unplugin-vue-components": "^0.27.3",
"vite": "^5.3.1",
+ "vite-plugin-compression": "^0.5.1",
"vitest": "^4.0.18"
}
},
@@ -3189,6 +3190,20 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3547,6 +3562,18 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4764,9 +4791,9 @@
}
},
"node_modules/strip-literal": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
- "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
"dev": true,
"dependencies": {
"js-tokens": "^9.0.1"
@@ -4947,28 +4974,25 @@
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
},
"node_modules/unimport": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz",
- "integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==",
+ "version": "3.14.6",
+ "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz",
+ "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==",
"dev": true,
"dependencies": {
- "acorn": "^8.16.0",
+ "@rollup/pluginutils": "^5.1.4",
+ "acorn": "^8.14.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
- "local-pkg": "^1.1.2",
- "magic-string": "^0.30.21",
- "mlly": "^1.8.0",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "pkg-types": "^2.3.0",
+ "fast-glob": "^3.3.3",
+ "local-pkg": "^1.0.0",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1",
+ "picomatch": "^4.0.2",
+ "pkg-types": "^1.3.0",
"scule": "^1.3.0",
- "strip-literal": "^3.1.0",
- "tinyglobby": "^0.2.15",
- "unplugin": "^2.3.11",
- "unplugin-utils": "^0.3.1"
- },
- "engines": {
- "node": ">=18.12.0"
+ "strip-literal": "^2.1.1",
+ "unplugin": "^1.16.1"
}
},
"node_modules/unimport/node_modules/confbox": {
@@ -5015,7 +5039,7 @@
"url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/unimport/node_modules/pkg-types": {
+ "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
@@ -5026,19 +5050,13 @@
"pathe": "^2.0.3"
}
},
- "node_modules/unimport/node_modules/unplugin": {
- "version": "2.3.11",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
- "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
- "dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "acorn": "^8.15.0",
- "picomatch": "^4.0.3",
- "webpack-virtual-modules": "^0.6.2"
- },
"engines": {
- "node": ">=18.12.0"
+ "node": ">= 10.0.0"
}
},
"node_modules/unplugin": {
@@ -5055,26 +5073,28 @@
}
},
"node_modules/unplugin-auto-import": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz",
- "integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==",
+ "version": "0.18.6",
+ "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz",
+ "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==",
"dev": true,
"dependencies": {
- "local-pkg": "^1.1.2",
- "magic-string": "^0.30.21",
- "picomatch": "^4.0.3",
- "unimport": "^5.6.0",
- "unplugin": "^2.3.11",
- "unplugin-utils": "^0.3.1"
+ "@antfu/utils": "^0.7.10",
+ "@rollup/pluginutils": "^5.1.3",
+ "fast-glob": "^3.3.2",
+ "local-pkg": "^0.5.1",
+ "magic-string": "^0.30.14",
+ "minimatch": "^9.0.5",
+ "unimport": "^3.13.4",
+ "unplugin": "^1.16.0"
},
"engines": {
- "node": ">=20.19.0"
+ "node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
- "@nuxt/kit": "^4.0.0",
+ "@nuxt/kit": "^3.2.2",
"@vueuse/core": "*"
},
"peerDependenciesMeta": {
@@ -5086,69 +5106,28 @@
}
}
},
- "node_modules/unplugin-auto-import/node_modules/confbox": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
- "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
- "dev": true
- },
- "node_modules/unplugin-auto-import/node_modules/local-pkg": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
- "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
+ "node_modules/unplugin-auto-import/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"dependencies": {
- "mlly": "^1.7.4",
- "pkg-types": "^2.3.0",
- "quansync": "^0.2.11"
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/unplugin-auto-import/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
},
"engines": {
- "node": ">=14"
+ "node": ">=16 || 14 >=14.17"
},
"funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/unplugin-auto-import/node_modules/pkg-types": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
- "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
- "dev": true,
- "dependencies": {
- "confbox": "^0.2.2",
- "exsolve": "^1.0.7",
- "pathe": "^2.0.3"
- }
- },
- "node_modules/unplugin-auto-import/node_modules/unplugin": {
- "version": "2.3.11",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
- "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
- "dev": true,
- "dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "acorn": "^8.15.0",
- "picomatch": "^4.0.3",
- "webpack-virtual-modules": "^0.6.2"
- },
- "engines": {
- "node": ">=18.12.0"
- }
- },
- "node_modules/unplugin-utils": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
- "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
- "dev": true,
- "dependencies": {
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sxzz"
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/unplugin-vue-components": {
@@ -5388,6 +5367,20 @@
}
}
},
+ "node_modules/vite-plugin-compression": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz",
+ "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "debug": "^4.3.3",
+ "fs-extra": "^10.0.0"
+ },
+ "peerDependencies": {
+ "vite": ">=2.0.0"
+ }
+ },
"node_modules/vitest": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
@@ -8189,6 +8182,17 @@
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
"dev": true
},
+ "fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -8439,6 +8443,16 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
"keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -9195,9 +9209,9 @@
"dev": true
},
"strip-literal": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
- "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
"dev": true,
"requires": {
"js-tokens": "^9.0.1"
@@ -9327,25 +9341,25 @@
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
},
"unimport": {
- "version": "5.7.0",
- "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz",
- "integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==",
+ "version": "3.14.6",
+ "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz",
+ "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==",
"dev": true,
"requires": {
- "acorn": "^8.16.0",
+ "@rollup/pluginutils": "^5.1.4",
+ "acorn": "^8.14.0",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
- "local-pkg": "^1.1.2",
- "magic-string": "^0.30.21",
- "mlly": "^1.8.0",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "pkg-types": "^2.3.0",
+ "fast-glob": "^3.3.3",
+ "local-pkg": "^1.0.0",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1",
+ "picomatch": "^4.0.2",
+ "pkg-types": "^1.3.0",
"scule": "^1.3.0",
- "strip-literal": "^3.1.0",
- "tinyglobby": "^0.2.15",
- "unplugin": "^2.3.11",
- "unplugin-utils": "^0.3.1"
+ "strip-literal": "^2.1.1",
+ "unplugin": "^1.16.1"
},
"dependencies": {
"confbox": {
@@ -9378,33 +9392,29 @@
"mlly": "^1.7.4",
"pkg-types": "^2.3.0",
"quansync": "^0.2.11"
- }
- },
- "pkg-types": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
- "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
- "dev": true,
- "requires": {
- "confbox": "^0.2.2",
- "exsolve": "^1.0.7",
- "pathe": "^2.0.3"
- }
- },
- "unplugin": {
- "version": "2.3.11",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
- "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
- "dev": true,
- "requires": {
- "@jridgewell/remapping": "^2.3.5",
- "acorn": "^8.15.0",
- "picomatch": "^4.0.3",
- "webpack-virtual-modules": "^0.6.2"
+ },
+ "dependencies": {
+ "pkg-types": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
+ "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+ "dev": true,
+ "requires": {
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
+ "pathe": "^2.0.3"
+ }
+ }
}
}
}
},
+ "universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true
+ },
"unplugin": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
@@ -9416,71 +9426,41 @@
}
},
"unplugin-auto-import": {
- "version": "21.0.0",
- "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz",
- "integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==",
+ "version": "0.18.6",
+ "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz",
+ "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==",
"dev": true,
"requires": {
- "local-pkg": "^1.1.2",
- "magic-string": "^0.30.21",
- "picomatch": "^4.0.3",
- "unimport": "^5.6.0",
- "unplugin": "^2.3.11",
- "unplugin-utils": "^0.3.1"
+ "@antfu/utils": "^0.7.10",
+ "@rollup/pluginutils": "^5.1.3",
+ "fast-glob": "^3.3.2",
+ "local-pkg": "^0.5.1",
+ "magic-string": "^0.30.14",
+ "minimatch": "^9.0.5",
+ "unimport": "^3.13.4",
+ "unplugin": "^1.16.0"
},
"dependencies": {
- "confbox": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
- "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
- "dev": true
- },
- "local-pkg": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz",
- "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
+ "brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"requires": {
- "mlly": "^1.7.4",
- "pkg-types": "^2.3.0",
- "quansync": "^0.2.11"
+ "balanced-match": "^1.0.0"
}
},
- "pkg-types": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
- "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+ "minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"requires": {
- "confbox": "^0.2.2",
- "exsolve": "^1.0.7",
- "pathe": "^2.0.3"
- }
- },
- "unplugin": {
- "version": "2.3.11",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
- "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
- "dev": true,
- "requires": {
- "@jridgewell/remapping": "^2.3.5",
- "acorn": "^8.15.0",
- "picomatch": "^4.0.3",
- "webpack-virtual-modules": "^0.6.2"
+ "brace-expansion": "^2.0.2"
}
}
}
},
- "unplugin-utils": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
- "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
- "dev": true,
- "requires": {
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3"
- }
- },
"unplugin-vue-components": {
"version": "0.27.5",
"resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz",
@@ -9604,6 +9584,17 @@
"rollup": "^4.20.0"
}
},
+ "vite-plugin-compression": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz",
+ "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.2",
+ "debug": "^4.3.3",
+ "fs-extra": "^10.0.0"
+ }
+ },
"vitest": {
"version": "4.0.18",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
diff --git a/package.json b/package.json
index 365735e..1ab31a1 100644
--- a/package.json
+++ b/package.json
@@ -50,9 +50,10 @@
"rollup-plugin-visualizer": "^7.0.1",
"sass": "^1.55.0",
"tailwindcss": "^4.1.18",
- "unplugin-auto-import": "^21.0.0",
+ "unplugin-auto-import": "^0.18.6",
"unplugin-vue-components": "^0.27.3",
"vite": "^5.3.1",
+ "vite-plugin-compression": "^0.5.1",
"vitest": "^4.0.18"
}
}
diff --git a/src/App.vue b/src/App.vue
index 8835083..dcf1bc5 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -19,7 +19,6 @@ import { onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { supabase } from '@/lib/supabase/client';
import { useTenantStore } from '@/stores/tenantStore';
-import { useEntitlementsStore } from '@/stores/entitlementsStore';
import { fetchDocsForPath } from '@/composables/useAjuda';
import AjudaDrawer from '@/components/AjudaDrawer.vue';
@@ -28,25 +27,14 @@ import AppOfflineOverlay from '@/components/AppOfflineOverlay.vue';
const route = useRoute();
const router = useRouter();
const tenantStore = useTenantStore();
-const entStore = useEntitlementsStore();
function isTenantArea(path = '') {
- return path.startsWith('/admin') || path.startsWith('/therapist');
-}
-
-function isPortalArea(path = '') {
- return path.startsWith('/portal');
-}
-
-function isSaasArea(path = '') {
- return path.startsWith('/saas');
+ return path.startsWith('/admin') || path.startsWith('/therapist') || path.startsWith('/configuracoes');
}
// ── Setup Wizard redirect ────────────────────────────────────────
async function checkSetupWizard() {
- // Só verifica em área de tenant
if (!isTenantArea(route.path)) return;
- // Não redireciona se já está no setup
if (route.path.includes('/setup')) return;
const uid = tenantStore.user?.id;
@@ -56,7 +44,6 @@ async function checkSetupWizard() {
if (!data) return;
- // Determina o kind do tenant ativo para saber qual flag checar
const activeMembership = tenantStore.memberships?.find((m) => m.id === tenantStore.activeTenantId);
const kind = activeMembership?.kind ?? tenantStore.activeRole ?? '';
const isClinic = kind.startsWith('clinic');
@@ -68,113 +55,16 @@ async function checkSetupWizard() {
}
}
-async function debugSnapshot(label = 'snapshot') {
- console.group(`🧭 [APP DEBUG] ${label}`);
- try {
- // 0) rota + meta
- console.log('route.fullPath:', route.fullPath);
- console.log('route.path:', route.path);
- console.log('route.name:', route.name);
- console.log('route.meta:', route.meta);
-
- // 1) storage
- console.groupCollapsed('📦 Storage');
- console.log('localStorage.tenant_id:', localStorage.getItem('tenant_id'));
- console.log('localStorage.currentTenantId:', localStorage.getItem('currentTenantId'));
- console.log('localStorage.tenant:', localStorage.getItem('tenant'));
- console.log('sessionStorage.redirect_after_login:', sessionStorage.getItem('redirect_after_login'));
- console.log('sessionStorage.intended_area:', sessionStorage.getItem('intended_area'));
- console.groupEnd();
-
- // 2) sessão auth (fonte real)
- const { data: authData, error: authErr } = await supabase.auth.getUser();
- if (authErr) console.warn('[auth.getUser] error:', authErr);
- const user = authData?.user || null;
- console.log('auth.user:', user ? { id: user.id, email: user.email } : null);
-
- // 3) profiles.role (identidade global)
- let profileRole = null;
- if (user?.id) {
- const { data: profile, error: pErr } = await supabase.from('profiles').select('role').eq('id', user.id).single();
-
- if (pErr) console.warn('[profiles] error:', pErr);
- profileRole = profile?.role || null;
- }
- console.log('profiles.role (global):', profileRole);
-
- // 4) memberships via RPC (fonte de verdade do tenantStore)
- let rpcTenants = null;
- if (user?.id) {
- const { data: rpcData, error: rpcErr } = await supabase.rpc('my_tenants');
- if (rpcErr) console.warn('[rpc my_tenants] error:', rpcErr);
- rpcTenants = rpcData ?? null;
- }
- console.log('rpc.my_tenants():', rpcTenants);
-
- // 5) stores (sempre logar)
- console.groupCollapsed('🏪 Stores (before optional loads)');
- console.log('tenantStore.activeTenantId:', tenantStore.activeTenantId);
- console.log('tenantStore.activeRole:', tenantStore.activeRole);
- console.log('tenantStore.memberships:', tenantStore.memberships);
- console.log('entStore.loaded:', entStore.loaded);
- console.log('entStore.tenantId:', entStore.activeTenantId || entStore.tenantId);
- console.groupEnd();
-
- // 6) IMPORTANTÍSSIMO: não carregar tenant fora da área tenant
- const path = route.path || '';
-
- if (isTenantArea(path)) {
- console.log('✅ Tenant area detected → will loadSessionAndTenant + entitlements');
- await tenantStore.loadSessionAndTenant();
-
- if (tenantStore.activeTenantId) {
- await entStore.loadForTenant(tenantStore.activeTenantId);
- }
-
- console.groupCollapsed('🏪 Stores (after tenant loads)');
- console.log('tenantStore.activeTenantId:', tenantStore.activeTenantId);
- console.log('tenantStore.activeRole:', tenantStore.activeRole);
- console.log('tenantStore.memberships:', tenantStore.memberships);
- console.log("entStore.can('online_scheduling.manage'):", entStore.can?.('online_scheduling.manage'));
- console.groupEnd();
-
- // Redireciona para o wizard se setup não foi concluído
- await checkSetupWizard();
- } else if (isPortalArea(path)) {
- console.log('🟣 Portal area detected → SKIP tenantStore.loadSessionAndTenant()');
- } else if (isSaasArea(path)) {
- console.log('🟠 SaaS area detected → SKIP tenantStore.loadSessionAndTenant()');
- } else {
- console.log('⚪ Other/public area detected → SKIP tenantStore.loadSessionAndTenant()');
- }
- } catch (e) {
- console.error('[APP DEBUG] snapshot error:', e);
- } finally {
- console.groupEnd();
- }
-}
-
-onMounted(async () => {
- // 🔥 PRIMEIRO LOG — TENANT ID BRUTO (mantive sua ideia)
- console.log('[SEU_TENANT_ID]', localStorage.getItem('tenant_id'));
-
- // snapshot inicial
- await debugSnapshot('mounted');
-
- // Carrega docs de ajuda para a rota inicial
+onMounted(() => {
fetchDocsForPath(route.path);
});
-// snapshot a cada navegação (isso é o que vai te salvar)
watch(
() => route.fullPath,
- async (to, from) => {
- await debugSnapshot(`route change: ${from} -> ${to}`);
- // Atualiza docs de ajuda ao navegar
+ () => {
fetchDocsForPath(route.path);
- // Verifica setup sempre que entrar em área tenant
if (isTenantArea(route.path) && tenantStore.loaded) {
- await checkSetupWizard();
+ checkSetupWizard();
}
}
);
diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts
index ac15611..0693796 100644
--- a/src/auto-imports.d.ts
+++ b/src/auto-imports.d.ts
@@ -6,68 +6,68 @@
// biome-ignore lint: disable
export {}
declare global {
- const EffectScope: typeof import('vue').EffectScope
- const computed: typeof import('vue').computed
- const createApp: typeof import('vue').createApp
- const customRef: typeof import('vue').customRef
- const defineAsyncComponent: typeof import('vue').defineAsyncComponent
- const defineComponent: typeof import('vue').defineComponent
- const effectScope: typeof import('vue').effectScope
- const getCurrentInstance: typeof import('vue').getCurrentInstance
- const getCurrentScope: typeof import('vue').getCurrentScope
+ const EffectScope: typeof import('vue')['EffectScope']
+ const computed: typeof import('vue')['computed']
+ const createApp: typeof import('vue')['createApp']
+ const customRef: typeof import('vue')['customRef']
+ const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
+ const defineComponent: typeof import('vue')['defineComponent']
+ const effectScope: typeof import('vue')['effectScope']
+ const getCurrentInstance: typeof import('vue')['getCurrentInstance']
+ const getCurrentScope: typeof import('vue')['getCurrentScope']
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
- const h: typeof import('vue').h
- const inject: typeof import('vue').inject
- const isProxy: typeof import('vue').isProxy
- const isReactive: typeof import('vue').isReactive
- const isReadonly: typeof import('vue').isReadonly
- const isRef: typeof import('vue').isRef
+ const h: typeof import('vue')['h']
+ const inject: typeof import('vue')['inject']
+ const isProxy: typeof import('vue')['isProxy']
+ const isReactive: typeof import('vue')['isReactive']
+ const isReadonly: typeof import('vue')['isReadonly']
+ const isRef: typeof import('vue')['isRef']
const isShallow: typeof import('vue').isShallow
- const markRaw: typeof import('vue').markRaw
- const nextTick: typeof import('vue').nextTick
- const onActivated: typeof import('vue').onActivated
- const onBeforeMount: typeof import('vue').onBeforeMount
- const onBeforeUnmount: typeof import('vue').onBeforeUnmount
- const onBeforeUpdate: typeof import('vue').onBeforeUpdate
- const onDeactivated: typeof import('vue').onDeactivated
- const onErrorCaptured: typeof import('vue').onErrorCaptured
- const onMounted: typeof import('vue').onMounted
- const onRenderTracked: typeof import('vue').onRenderTracked
- const onRenderTriggered: typeof import('vue').onRenderTriggered
- const onScopeDispose: typeof import('vue').onScopeDispose
- const onServerPrefetch: typeof import('vue').onServerPrefetch
- const onUnmounted: typeof import('vue').onUnmounted
- const onUpdated: typeof import('vue').onUpdated
- const onWatcherCleanup: typeof import('vue').onWatcherCleanup
- const provide: typeof import('vue').provide
- const reactive: typeof import('vue').reactive
- const readonly: typeof import('vue').readonly
- const ref: typeof import('vue').ref
- const resolveComponent: typeof import('vue').resolveComponent
- const shallowReactive: typeof import('vue').shallowReactive
- const shallowReadonly: typeof import('vue').shallowReadonly
- const shallowRef: typeof import('vue').shallowRef
- const toRaw: typeof import('vue').toRaw
- const toRef: typeof import('vue').toRef
- const toRefs: typeof import('vue').toRefs
- const toValue: typeof import('vue').toValue
- const triggerRef: typeof import('vue').triggerRef
- const unref: typeof import('vue').unref
- const useAttrs: typeof import('vue').useAttrs
- const useCssModule: typeof import('vue').useCssModule
- const useCssVars: typeof import('vue').useCssVars
- const useId: typeof import('vue').useId
- const useModel: typeof import('vue').useModel
- const useSlots: typeof import('vue').useSlots
- const useTemplateRef: typeof import('vue').useTemplateRef
- const watch: typeof import('vue').watch
- const watchEffect: typeof import('vue').watchEffect
- const watchPostEffect: typeof import('vue').watchPostEffect
- const watchSyncEffect: typeof import('vue').watchSyncEffect
+ const markRaw: typeof import('vue')['markRaw']
+ const nextTick: typeof import('vue')['nextTick']
+ const onActivated: typeof import('vue')['onActivated']
+ const onBeforeMount: typeof import('vue')['onBeforeMount']
+ const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
+ const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
+ const onDeactivated: typeof import('vue')['onDeactivated']
+ const onErrorCaptured: typeof import('vue')['onErrorCaptured']
+ const onMounted: typeof import('vue')['onMounted']
+ const onRenderTracked: typeof import('vue')['onRenderTracked']
+ const onRenderTriggered: typeof import('vue')['onRenderTriggered']
+ const onScopeDispose: typeof import('vue')['onScopeDispose']
+ const onServerPrefetch: typeof import('vue')['onServerPrefetch']
+ const onUnmounted: typeof import('vue')['onUnmounted']
+ const onUpdated: typeof import('vue')['onUpdated']
+ const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
+ const provide: typeof import('vue')['provide']
+ const reactive: typeof import('vue')['reactive']
+ const readonly: typeof import('vue')['readonly']
+ const ref: typeof import('vue')['ref']
+ const resolveComponent: typeof import('vue')['resolveComponent']
+ const shallowReactive: typeof import('vue')['shallowReactive']
+ const shallowReadonly: typeof import('vue')['shallowReadonly']
+ const shallowRef: typeof import('vue')['shallowRef']
+ const toRaw: typeof import('vue')['toRaw']
+ const toRef: typeof import('vue')['toRef']
+ const toRefs: typeof import('vue')['toRefs']
+ const toValue: typeof import('vue')['toValue']
+ const triggerRef: typeof import('vue')['triggerRef']
+ const unref: typeof import('vue')['unref']
+ const useAttrs: typeof import('vue')['useAttrs']
+ const useCssModule: typeof import('vue')['useCssModule']
+ const useCssVars: typeof import('vue')['useCssVars']
+ const useId: typeof import('vue')['useId']
+ const useModel: typeof import('vue')['useModel']
+ const useSlots: typeof import('vue')['useSlots']
+ const useTemplateRef: typeof import('vue')['useTemplateRef']
+ const watch: typeof import('vue')['watch']
+ const watchEffect: typeof import('vue')['watchEffect']
+ const watchPostEffect: typeof import('vue')['watchPostEffect']
+ const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
- export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
+ export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}
diff --git a/src/features/patients/PatientsListPage.vue b/src/features/patients/PatientsListPage.vue
index efc9996..82b89b3 100644
--- a/src/features/patients/PatientsListPage.vue
+++ b/src/features/patients/PatientsListPage.vue
@@ -15,21 +15,21 @@
|--------------------------------------------------------------------------
-->