0004 — PWA + Mobile-First + Notificações Push
Status
Accepted · 2026-04-26 · Autor: Alexandre + Claude.
Contexto
Clínicas brasileiras de pequeno-médio porte usam mobile como cliente principal de gestão. App nativo seria custo alto pra MVP. PWA resolve:
- Instalável (ícone na home iOS/Android)
- Funciona offline (limitado, mas read básico ok)
- Push notifications nativas
- Single codebase (web + mobile)
Decisão
1. PWA via vite-plugin-pwa (Workbox)
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({ plugins: [ VitePWA({ registerType: 'autoUpdate', manifest: { name: 'ClinicGestor', short_name: 'ClinicGestor', theme_color: '#1e40af', background_color: '#ffffff', display: 'standalone', icons: [ { src: 'pwa-192x192.png', sizes: '192x192', type: 'image/png' }, { src: 'pwa-512x512.png', sizes: '512x512', type: 'image/png' }, { src: 'pwa-maskable-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable', }, ], }, workbox: { globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], runtimeCaching: [ { urlPattern: /^https:\/\/<ref>\.supabase\.co\/storage\/.*$/, handler: 'StaleWhileRevalidate', options: { cacheName: 'supabase-storage', expiration: { maxAgeSeconds: 86400 } }, }, ], }, }), ],});Service worker registrado automaticamente. Nunca navigator.serviceWorker.register(...) manual.
2. Mobile-first obrigatório
Toda tela testada em:
- 375×667 (iPhone SE, iPhone 8) — pior caso
- 390×844 (iPhone 12/13/14)
Regras:
- Tap targets ≥ 44×44 px (Apple/Material spec)
- Fonte UI ≥ 14px; corpo ≥ 16px
- Zero scroll horizontal em 375px
- Inputs com
inputmodeapropriado (numeric,tel,email) eautocomplete - Gráficos com
<ResponsiveContainer>(Recharts) - Safe-area via
env(safe-area-inset-*)em shells (notch iPhone, etc) - Sidebar vira drawer em
< md(768px) - Mobile: container 1 coluna; desktop: 2-3 colunas conforme conteúdo
min-h-dvhem vez deh-screen(iOS Safari quebra comh-screen)
3. Notificações push (opt-in contextual)
Princípio: nunca pedir permissão no load da página. Pedir após ação relacionada.
Exemplos:
- Clínica clica “Salvar consulta” → toast: “Quer ser avisado quando o paciente confirmar?” → botão “Ativar” →
Notification.requestPermission()aqui - Consultor clica “Acompanhar status do contrato” → modal: “Te aviso quando assinar?” → opt-in
Implementação:
- Edge function
register-push-subscriptionsalva subscription cifrada (p256dh_encrypted,auth_encrypted) empush_subscriptions - Edge function
send-push(web-push library) envia payload mínimo - Payload sem PII: apenas metadata + deeplink (ex:
{ "type": "appointment_confirmed", "appointment_id": "uuid" }). App carrega detalhes ao abrir.
CREATE TABLE public.push_subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES auth.users(id), clinic_id UUID, consultancy_id UUID, endpoint_encrypted BYTEA NOT NULL, p256dh_encrypted BYTEA NOT NULL, auth_encrypted BYTEA NOT NULL, user_agent TEXT, created_at TIMESTAMPTZ DEFAULT NOW(), expires_at TIMESTAMPTZ, UNIQUE (user_id, endpoint_encrypted));VAPID keys (web-push) no Vault: VAPID_PUBLIC_KEY (público — front consome), VAPID_PRIVATE_KEY (Vault — só send-push).
4. Light + Dark obrigatórios
Toda tela testada em ambos. Configuração via data-theme="dark" em <html> ou <body>.
5. Acessibilidade WCAG 2.1 AA
eslint-plugin-jsx-a11yobrigatório- Foco visível (
focus-visible:ring-2) - Contraste enforced (algoritmo de derivação de cores no ADR 0003)
- ARIA correto (
aria-label,aria-describedby,role) - Navegação por teclado (Tab + Enter + Escape funcionam)
Consequências
Positivas
- Single codebase web + mobile via PWA (zero custo de app nativo no MVP)
- Offline básico funciona (cache via Workbox)
- Engagement via push notifications
- Acessibilidade enforced por lint
- Performance mobile (font bundled, fontes locais, lazy loading)
Negativas
- iOS PWA tem limitações vs Android (storage menor, push iOS 16.4+ apenas, etc)
- Mobile-first exige disciplina extra em testes
- Push subscription cifrada adiciona overhead
Riscos
- iOS Safari mudar comportamento PWA (já aconteceu várias vezes)
- Mitigação: testar em iOS real cada release; not rely on iOS-specific features
- Service worker bug bloquear app
- Mitigação:
registerType: 'autoUpdate'+ skipWaiting controlado
- Mitigação:
Alternativas consideradas
1. App nativo (React Native ou Flutter)
- Prós: melhor performance mobile; APIs nativas
- Contras: custo alto (build, app stores, manutenção); single codebase quebra (precisa web também). Rejeitado pra MVP. Avaliar em F3+ se PWA não atender.
2. Hybrid (Capacitor / Cordova)
- Prós: web wrapped em native shell
- Contras: limitações de performance; complexidade extra. Rejeitado.
3. Web responsivo sem PWA
- Prós: simplicidade
- Contras: sem instalabilidade, sem push, sem offline. Rejeitado — PWA é diferencial competitivo.
Atualizações de documentos exigidas
- AGENTS.md §5.9 (Mobile-first + PWA)
docs/design-system.md§PWA + §Mobile-Firstdocs/edge-functions/send-push.md+register-push-subscription.md
Referências
- ADR 0003 (design system)
- AGENTS.md §5.9
- BLUEPRINT.md §6 (princípios)