Pular para o conteúdo

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)

vite.config.ts
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 inputmode apropriado (numeric, tel, email) e autocomplete
  • 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-dvh em vez de h-screen (iOS Safari quebra com h-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-subscription salva subscription cifrada (p256dh_encrypted, auth_encrypted) em push_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-a11y obrigató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

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-First
  • docs/edge-functions/send-push.md + register-push-subscription.md

Referências

  • ADR 0003 (design system)
  • AGENTS.md §5.9
  • BLUEPRINT.md §6 (princípios)