From 7fedf45cd7c344f529e1dac3785d7b15343ca57d Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sun, 9 Aug 2026 23:09:42 -0300 Subject: [PATCH] =?UTF-8?q?docs(C108,D6):=20planos=20de=20inten=C3=A7?= =?UTF-8?q?=C3=A3o=20=E2=80=94=20sino=20de=20notifica=C3=A7=C3=B5es=20(ler?= =?UTF-8?q?=20ao=20abrir=20+=20modal=20desktop)=20e=20push=20no=20device?= =?UTF-8?q?=20em=20produ=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../push-notificacoes-dispositivo-producao.md | 66 ++++++++++++++++ ...notificacoes-leitura-auto-modal-desktop.md | 79 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 docs/plans/push-notificacoes-dispositivo-producao.md create mode 100644 docs/plans/sino-notificacoes-leitura-auto-modal-desktop.md diff --git a/docs/plans/push-notificacoes-dispositivo-producao.md b/docs/plans/push-notificacoes-dispositivo-producao.md new file mode 100644 index 00000000..d3c9d9ae --- /dev/null +++ b/docs/plans/push-notificacoes-dispositivo-producao.md @@ -0,0 +1,66 @@ +# D6 — Push no dispositivo funcionando de ponta a ponta (notificações nativas em produção) + +Status: registrado +Atualizado em: 2026-08-09 +Issue: #521 +Priority: P1 +Model: cursor-grok-4.5-medium +Model-local: deepseek-v4-high +Impeccable: A — N/A sem superfície UI nova (a tela de opt-in já existe; entrega é verificação/config) +Canvas UI: N/A — sem UI +Appetite: ~1 dia eng + passos humanos (envs de produção / consentimento) — verificação ponta a ponta com device real +Responsável: — + +## Intenção + +D2 entregou push + sino em código, mas na prática o device do usuário não recebe notificação: só o mecanismo interno ao app (sino) funciona. Eventos da campanha devem disparar também o banner nativo do sistema (a notificação padrão do device), inclusive com o app fechado. + +## Persona e fluxo + +- **Persona / contexto:** coordenador/assessor que ativou (ou quer ativar) avisos push no perfil; liderança em campo. +- **Job principal:** ser avisado no device quando algo importante acontece na campanha, sem precisar abrir o app. +- **Fluxo desejado:** ativa "Avisos push" em `/campanha/perfil` (consentimento LGPD) → evento da campanha (novo update de município, novo apoiador, atividade que precisa de atenção, convite aceito) → banner nativo no device → toque abre a tela do evento dentro do `/campanha`. +- **Anti-goals de produto:** não reescrever a mecânica de push existente; não criar canal novo (WhatsApp continua fora — D3–D5 `wontfix`); push continua exclusivo da vertical `/campanha` (decisão travada do D2). + +## Objetivo e aceite + +- Com as VAPID keys configuradas em produção e o consentimento `campanha-notificacoes-push` publicado no admin, um dispositivo real optado recebe o banner nativo para cada tipo de evento que hoje gera notificação in-app — e o toque no banner abre a tela correta do evento. +- O diagnóstico da cadeia fecha com evidência para cada elo: envs de produção (VAPID + `NEXT_PUBLIC_VAPID_PUBLIC_KEY`), consentimento publicado no admin, inscrição push registrada por device, entrega confirmada pela plataforma de push, banner exibido no OS. Bloqueio encontrado → corrigido ou escalado com dono nomeado (env → Vercel; consentimento → assessoria jurídica via lote Onda 0). +- Limitação documentada no produto: iOS só recebe push com o PWA instalado na tela inicial (a UI do perfil já comunica) — não é defeito a corrigir aqui. + +## Dados (intenção) + +Dados: N/A — verificação de entrega ponta a ponta; nenhuma métrica nova de produto. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** cadeia já implementada em `src/utilities/notification/` (`createCampaignNotification.ts`, `sendCampaignPush.ts` — web-push/VAPID, `notificationEvents.ts` — eventos que geram notificação); service worker em `src/utilities/campaignPwa.ts` (handlers push + notificationclick); opt-in em `src/components/campaign/auth/CampaignPushNotificationsCard.tsx` (`/campanha/perfil`); chave de consentimento em `src/lib/campaignConsentKeys.ts`. +- **Config de produção:** Vercel envs `VAPID_PUBLIC_KEY` / `VAPID_PRIVATE_KEY` / `NEXT_PUBLIC_VAPID_PUBLIC_KEY` (documentadas no `.env.example`); consentimento criado por admin (texto já existe no lote Onda 0 em `src/lib/onda0ConsentTexts.ts`). +- **Risco de acoplamento:** fail-closed de consentimento (nunca quebrar — sem consentimento publicado, a ativação continua bloqueada); assimetria de papéis na notificação por destinatário. + +## Dependências + +- D2 (entregue, imutável). Nenhuma dura nova. + +## Fora de escopo + +- WhatsApp Business API / bridges (D3–D5, `wontfix`). +- Agrupamento/dedupe de notificações e expurgo (questões em aberto do D2 — item próprio se quiser). +- Convite/nudge de ativação de push dentro do sino (ver questão em aberto). + +## Rabbit holes de produto + +- **Depurar código que já está correto.** Se alguém "só completar" sem checar config primeiro, perde tempo reescrevendo a cadeia. **Corte:** fechar o diagnóstico elo a elo (envs → consentimento → inscrição → entrega); só tocar código com evidência de defeito. +- **Querer "consertar" iOS Safari sem PWA instalado.** Limitação de plataforma. **Corte:** comunicar e seguir; validar entrega no Android. +- **Push para o site público.** Decisão travada do D2: push só em `/campanha`. **Corte:** manter. + +## Questões em aberto (produto) + +- **Staff que não optou merece um convite para ativar push (nudge no sino)?** **Opções:** A) não — só o card do perfil | B) convite discreto na primeira abertura do sino | C) banner amplo. **Recomendação:** B, mas como item separado — não inflar esta verificação. _(decisão do gate)_ +- **Onde validar a entrega em device real?** **Opções:** A) Android Chrome obrigatório; iOS (PWA instalado) opcional nesta entrega | B) ambos obrigatórios. **Recomendação:** A — o Android valida a cadeia inteira; o iOS exige aparelho + instalação e a limitação já é conhecida. + +## Referências + +- D2 — `docs/plans/notifications.md` (Issue #29, entregue; imutável) +- `AGENTS.md` — Consent fail-closed; lote jurídico Onda 0; D3–D5 `wontfix` +- Canvas UI (gate): N/A diff --git a/docs/plans/sino-notificacoes-leitura-auto-modal-desktop.md b/docs/plans/sino-notificacoes-leitura-auto-modal-desktop.md new file mode 100644 index 00000000..73597779 --- /dev/null +++ b/docs/plans/sino-notificacoes-leitura-auto-modal-desktop.md @@ -0,0 +1,79 @@ +# C108 — Sino de notificações: ler ao abrir + painel limpo + modal centrado no desktop + +Status: registrado +Atualizado em: 2026-08-09 +Issue: #522 +Priority: P2 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: C — encaixe no painel do sino (shell do `/campanha`, mobile + desktop) +Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-17/canvases/plan-c108-ui-draft.canvas.tsx +Appetite: ~0,5–1 dia eng; um encaixe no painel existente, sem superfície nova +Responsável: — + +## Intenção + +O staff abre o sino de notificações e precisa de um clique extra ("Marcar todas como lidas") para algo que deveria resolver na abertura — e depois ainda de "Fechar". O painel carrega título, contador e botões que não ajudam a decisão; no desktop ele é um sheet ancorado na base, quando o esperado é um modal central que some ao clicar fora. + +## Persona e fluxo + +- **Persona / contexto:** coordenador/assessor/liderança em `/campanha` — mesa (desktop) ou campo (mobile), abrindo o sino para ver o que aconteceu. +- **Job principal:** ver os avisos e voltar ao trabalho; abrir já deixa tudo lido, sem interação extra. +- **Fluxo desejado:** toca no sino → painel abre (desktop: modal central com X no canto superior direito; mobile: sheet de base) → lista carrega e marca tudo como lido no ato (badge do sino zera) → toca num item e vai para a tela, ou fecha por X / clique fora / swipe. +- **Anti-goals de produto:** não criar leitura individual/por item; não virar central de notificações com configurações e histórico; não mexer nos gatilhos de abertura (slot do header). + +### Esboço de fluxo (C) + +```text +[Abrir o sino] + → desktop: modal centralizado com X no canto superior direito; fora = fecha + → mobile: bottom sheet de base; swipe/backdrop fecha + → lista carrega e marca tudo como lido no ato (badge zera, sem clique) + → [item?] → navega para a tela do evento e fecha o painel + → [fechar] → X / clique fora / swipe (sem botão "Fechar") +``` + +## Objetivo e aceite + +- Abrir o painel marca todas as notificações do usuário como lidas automaticamente — o badge do sino zera sem nenhuma ação (o "Marcar todas como lidas" deixa de existir como botão). +- O painel não exibe título "Notificações" nem botão "Fechar", em nenhum tamanho de tela. +- No desktop o painel abre como modal centralizado, com X de fechar no canto superior direito e clique-fora fechando — usando o componente shadcn já existente no repo (precedente do par responsivo Dialog/Drawer em C94). +- No mobile continua bottom sheet; o destaque visual de "não lida" na lista deixa de fazer sentido (nada é não lido depois da abertura) e pode sair. +- Guardrail: leader lockdown intacto — notificações continuam por destinatário (`recipient` = campaignUser); nenhuma mudança de acesso, consentimento ou schema. + +## Dados (intenção) + +Dados: N/A — contagem já existe (badge e `read_at`); nenhuma métrica nova. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `src/components/campaign/shell/CampaignNotificationBell.tsx` (painel atual: Drawer + footer com botões); ação já existente `markAllCampaignNotificationsRead` em `src/app/(campaign)/campanha/actions/notifications.ts` — passa a ser disparada na abertura; `src/components/ui/dialog.tsx` (X de fechar já existe) + `src/components/ui/Drawer.tsx`. +- **Precedente a olhar:** par responsivo Dialog/Drawer do `CalendarFeedDialog` (C94) e o padrão "sheet sem título" dos itens mobile recentes (C106/C107). +- **Risco de acoplamento:** shell do `/campanha` (headers desktop/mobile) — o gatilho do sino é slot existente; não trocar o contrato. + +## Dependências + +- Nenhuma dura. C106/C107 (superfícies diferentes) são independentes. + +## Fora de escopo + +- Leitura individual/por item ou "deixar X como não lida" — decisão de não fazer (anti-goal). +- Push para o device: D6. +- Central de notificações (histórico, expurgo, preferências) — não existe hoje; não inventar aqui. + +## Rabbit holes de produto + +- **Marcar no fechar em vez de no abrir.** Se alguém "só completar" com marcação ao fechar, o badge não zera na abertura — o usuário pediu explicitamente o contrário. **Corte:** marcar na abertura, ponto. +- **Personalizar a lista.** Agrupar, filtrar, "não perturbe" vira outra superfície. **Corte:** apenas remover título/contador/botões e trocar o invólucro no desktop. + +## Questões em aberto (produto) + +- **A linha "X não lidas / Tudo em dia" do header some junto com o título?** **Opções:** A) remover — a contagem vive no badge do sino | B) manter. **Recomendação:** A — com a marcação automática ela vira sempre "Tudo em dia", sem função. _(assumido — validar)_ +- **Destaque visual de não lidas na lista:** após a marcação automática nunca aparece. **Opções:** A) remover | B) manter. **Recomendação:** A. _(assumido — validar)_ + +## Referências + +- D2 — `docs/plans/notifications.md` (Issue #29, entregue; imutável — este item refina o sino dela, não edita o plano) +- Canvas UI (gate): plan-c108-ui-draft.canvas.tsx +- C94 — `docs/CHANGELOG-AGENTS.md` (2026-08-09): precedente Dialog/Drawer responsivo +- `AGENTS.md` — shell do `/campanha` e leader lockdown