From 3dba17b4857fa484ed800d541637150440b42e7d Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Thu, 27 Aug 2026 18:14:38 -0300 Subject: [PATCH] =?UTF-8?q?docs(plans):=20C147=E2=80=93C151=20=E2=80=94=20?= =?UTF-8?q?sync=20Google=20Calendar,=20modais=20da=20agenda,=20OAuth,=20ca?= =?UTF-8?q?lend=C3=A1rio=20principal,=20evento=20p=C3=BAblico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../c147-sync-google-calendar-producao.md | 82 ++++ .../c148-modais-agenda-desktop-ui-draft.html | 376 ++++++++++++++++++ docs/plans/c148-modais-agenda-desktop.md | 93 +++++ .../c149-conectar-google-oauth-ui-draft.html | 178 +++++++++ docs/plans/c149-conectar-google-oauth.md | 91 +++++ ...-principal-e-inscricao-staff-ui-draft.html | 192 +++++++++ ...-calendario-principal-e-inscricao-staff.md | 100 +++++ ...151-atividade-evento-publico-ui-draft.html | 274 +++++++++++++ docs/plans/c151-atividade-evento-publico.md | 94 +++++ 9 files changed, 1480 insertions(+) create mode 100644 docs/plans/c147-sync-google-calendar-producao.md create mode 100644 docs/plans/c148-modais-agenda-desktop-ui-draft.html create mode 100644 docs/plans/c148-modais-agenda-desktop.md create mode 100644 docs/plans/c149-conectar-google-oauth-ui-draft.html create mode 100644 docs/plans/c149-conectar-google-oauth.md create mode 100644 docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html create mode 100644 docs/plans/c150-calendario-principal-e-inscricao-staff.md create mode 100644 docs/plans/c151-atividade-evento-publico-ui-draft.html create mode 100644 docs/plans/c151-atividade-evento-publico.md diff --git a/docs/plans/c147-sync-google-calendar-producao.md b/docs/plans/c147-sync-google-calendar-producao.md new file mode 100644 index 00000000..7d86818e --- /dev/null +++ b/docs/plans/c147-sync-google-calendar-producao.md @@ -0,0 +1,82 @@ +# Google Calendar: sync configurado em produção não funciona (diagnóstico + fix) + +Status: rascunho +Atualizado em: 2026-08-27 +Issue: #932 +Priority: P1 +Model: composer-2.5 +Impeccable: A — N/A (sem UI nova; a pill/dialog de estado já existem) +Rascunho UI: N/A — sem UI +Appetite: ~1–2 dias eng; um outcome verificável — com a integração configurada no Google e no Teqo, criar/editar uma atividade reflete no calendário em minutos e o estado do sync é honesto (nunca silenciosamente quebrado) +Responsável: — + +## Intenção + +"Eu configurei a integração do Google Calendar para a agenda da campanha em produção, mas ela não funciona — não está sincronizando. Descubra por que e conserte." A integração foi entregue (motor de reconciliação bidirecional, pill de estado, webhook), mas na prática a agenda oficial da campanha e o Google Calendar não estão conversando — e nada na interface diz por que. O problema não é "falta feature": é uma integração existente que falha em silêncio. Este item existe para (1) diagnosticar a causa em produção, (2) consertá-la e (3) garantir que, daqui em diante, uma falha de sync nunca passe despercebido — o estado mostrado na agenda precisa ser honesto, inclusive quando o canal de push do Google expira sem ninguém perceber. + +## Persona e fluxo + +- **Persona / contexto:** o coordenador (ou o próprio deputado) que mantém `/campanha/agenda` como fonte da agenda oficial; já configurou a service account no Google Cloud e o calendário no Teqo, e confia que "configurado" significa "funcionando". +- **Job principal:** criar/editar uma atividade no Teqo e vê-la aparecer no calendário do Google em minutos — ou, se não aparecer, saber imediatamente o motivo na própria agenda. +- **Fluxo desejado:** configura a integração uma vez → usa a agenda normalmente → quando algo quebra (credencial revogada, canal de push expirado, calendarId errado), a pill de estado diz o que aconteceu em vez de fingir que está tudo bem → o coordenador (ou o runbook) resolve sem depender de sorte ou de uma passada de sync que ninguém disparou. +- **Anti-goals de produto:** não virar um projeto de OAuth de usuário (troca de modelo de credencial), não redesenhar os modais de estado, não reconstruir o motor de sync que já existe e funciona em dev. + +## Objetivo e aceite + +- Com a integração configurada no Google e no Teqo, criar ou editar uma atividade em `/campanha/atividades` reflete no calendário do Google em minutos. +- O estado do sync exibido na agenda (pill + dialog existentes) é honesto: se o sync está quebrado, ele diz que está quebrado e por quê — nunca "sincronizado" quando não está, nunca silêncio. +- Se o canal de push do Google expirar e nenhuma passada de sync rodar para renová-lo, existe um caminho de renovação que não depende de sorte (hipótese a validar no diagnóstico). +- Guardrails: o Teqo permanece a fonte da verdade — uma falha do Google nunca corrompe nem bloqueia os dados de atividade no Teqo; a integração continua opcional e fail-closed (sem credencial → agenda funciona normal, estado "não configurado"); o lockdown de liderança e o modelo de municípios ficam intocados. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — este item não cria superfície de dados nova; usa o estado já registrado no doc de sync (lastError, lastErrorAt, pushChannelError) como insumo do diagnóstico. +- **Decisões desbloqueadas:** coordenador/operador decide, ao ler o estado honesto, se o problema é configuração no Google, env no servidor ou canal de push — sem precisar abrir o banco. +- **Forma:** adiada ao plano de implementação — aqui só a restrição: o estado precisa ser legível por um humano não-técnico na UI existente. + +## Dados da decisão (literais) + +- `GOOGLE_CALENDAR_SERVICE_ACCOUNT_KEY` — base64 do JSON da service account GCP (conforme `.env.example:61–67: "cat key.json | base64 -w0"`; a service account precisa de permissão "make changes to events" no calendário da campanha). Em produção, o valor vive em `~/stack/teqo-1313.env` no homeserver. +- Comportamento fail-closed já travado: env ausente/malformada → credencial `null` → status derivado `not-configured` → agenda do Teqo segue funcionando sem espelho no Google; nenhum erro além da pill. +- O `calendarId` é configurado por admin no doc único da collection de sync (campo admin-only); um valor errado aqui é causa plausível de "configurado mas não sincroniza". + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `src/utilities/googleCalendarSync.ts` (motor de reconciliação), `src/utilities/googleCalendarClient.ts` (JWT via `jose`, timeout 15s/hop), `src/utilities/googleCalendarSyncHooks.ts` (hooks afterChange/afterDelete de `activity`, budget de 5s — C114-LOCK), `src/app/(campaign)/campanha/agenda/google-webhook/[secret]/route.ts` (push), `src/components/campaign/agenda/AgendaGoogleSyncChrome.tsx` + `GoogleCalendarSyncDialog.tsx` (estado exibido), `src/collections/GoogleCalendarSync.ts` (doc único, `calendarId`). +- **Hipóteses de causa-raiz a testar, em ordem de barato→caro:** env ausente no container de produção (fail-closed engoliu o problema); service account sem compartilhamento no calendário ou credencial revogada; `calendarId` errado; canal de push expirado (30 dias) sem passada que o renove; webhook inalcançável atrás do túnel Cloudflare (`getCampaignInviteBaseURL`/`NEXT_PUBLIC_SITE_URL`). +- **Precedente a olhar:** docs/plans/c114-google-calendar-push-agenda-oficial.md (+ -impl), c115 (bidirecional), c114-lock (budget de hooks), c122; Issues #635/#636 (closed). +- **Risco de acoplamento:** o budget de 5s dos hooks (C114-LOCK) existe para o write path nunca esperar o Google — o guardrail "falha não é silenciosa" deve vir de telemetria/estado, não de deixar o hook lento; não reconstruir o motor, não trocar o modelo de credencial (C149 é outro item). + +## Dependências + +- Nenhuma dura. C114/C115 (entregues, closed) são pré-condição de código — já estão em `main`/produção. + +## Fora de escopo + +- C149 — troca do modelo de credencial para OAuth de usuário (outro item; aqui a service account permanece). +- C148 — layout/redesign dos modais de estado (aqui só se garante que o conteúdo do estado é honesto). +- Reconstrução do motor de reconciliação bidirecional; introdução de cron/queue como infra nova (só se o diagnóstico provar ser a causa-raiz mínima). + +## Rabbit holes de produto + +- **"Já que vou tocar no sync, implemento OAuth com conta de usuário do Google."** Se alguém "só completar": reescreve o modelo de credencial inteiro, atrasa o fix de um bug P1. **Corte neste item:** service account permanece; troca é C149. +- **"Aproveito e redesenho a pill/dialog do sync."** Se alguém "só completar": projeto de UI para mascarar um problema de backend. **Corte neste item:** estado honesto no conteúdo; forma é C148. +- **"Diagnóstico virou auditoria do motor inteiro + suíte de e2e de sync."** Se alguém "só completar": semana de engenharia para um outcome de 1–2 dias. **Corte neste item:** diagnóstico orientado pelo checklist (env → credencial → calendarId → estado do doc/pushChannelError → canal de push → webhook), fix da causa-raiz e o guardrail de não-silêncio. + +## Questões em aberto (produto) + +- **O fix pode incluir operação no homeserver (p. ex. env faltando em `~/stack/teqo-1313.env`, reinício de container)?** **Opções:** A) sim, e o que for configuração vira linha no runbook; B) só código, operação fica com humano. **Recomendação:** A — se a causa-raiz for configuração, registrar o passo no runbook `docs/ops/teqo-1313-deploy.md` para não depender de memória. _(assumido — validar com produto)_ +- **Quer telemetria mínima no log do servidor quando o hook de sync aborta (hoje é `logger.debug`/estado no doc, invisível no write path)?** **Opções:** A) log de erro estruturado no abort; B) manter só estado no doc. **Recomendação:** A — a falha silenciosa foi exatamente o sintoma relatado; log com nível de erro no abort é a mudança mínima que fecha a classe do problema. +- **A renovação do canal de push precisa de gatilho garantido (a cada passada vira "renova só se algo disparar passada")?** **Opções:** A) renovação atrelada a um gatilho que comprovadamente roda (a auto-retry no load da agenda, se diagnosticada como suficiente); B) infra mínima de cron. **Recomendação:** A — cron/queue só se o diagnóstico provar que nenhum gatilho existente roda com frequência suficiente; decidir depois do diagnóstico, não antes. + +## Referências + +- GitHub Issue: #— (a registrar via `pnpm agent:register`) +- `src/utilities/googleCalendarSync.ts`, `src/utilities/googleCalendarClient.ts`, `src/utilities/googleCalendarSyncHooks.ts` +- `src/collections/GoogleCalendarSync.ts`, `src/collections/Activity.ts` +- `src/app/(campaign)/campanha/agenda/google-webhook/[secret]/route.ts` +- `src/components/campaign/agenda/AgendaGoogleSyncChrome.tsx`, `GoogleCalendarSyncDialog.tsx` +- `.env.example:61–67` — literal e contrato fail-closed do env +- docs/plans/c114-google-calendar-push-agenda-oficial.md (+ -impl), docs/plans/c115-google-calendar-edicao-bidirecional.md (+ -impl), docs/plans/c114-lock-hooks-do-sync-do-google-calendar-seguram-impl.md +- `docs/ops/teqo-1313-deploy.md` — runbook do homeserver (destino das conclusões de configuração) +- `AGENTS.md` — produção = homeserver (`teqo_1313`), env em `~/stack/teqo-1313.env`, sem Vercel diff --git a/docs/plans/c148-modais-agenda-desktop-ui-draft.html b/docs/plans/c148-modais-agenda-desktop-ui-draft.html new file mode 100644 index 00000000..8e570bd5 --- /dev/null +++ b/docs/plans/c148-modais-agenda-desktop-ui-draft.html @@ -0,0 +1,376 @@ + + + + + + + UI draft — C148 · Modais da agenda no desktop + + +
+

C148 · Modais da agenda usáveis no desktop

+

+ Assessor/coordenador no desktop, criando atividade e sincronizando a agenda. Zinc neutro + + um acento (indigo) só no CTA primário. Estático — só medidas e encaixe. +

+ + +
+

+ Cena 1 · Desktop ~1280px — modal de criação de atividade +

+

+ Largura generosa, paddings contidos, Início/Término lado a lado (cada um com data+hora na + mesma linha), corpo com rolagem interna e rodapé fixo. +

+
+ +
+
+
+

Nova atividade

+ × +
+
+
+
+ Título +
+ Título da atividade +
+
+
+
+ Tipo +
+ Selecione o tipo▾ +
+
+
+ Status +
+ Selecione o status▾ +
+
+
+
+
+ Município +
+ Selecione o município▾ +
+
+
+ Localidade +
+ Bairro, praça ou referência +
+
+
+
+
+ Início +
+
+ dd/mm/aaaa +
+
+ --▾ +
+
+ --▾ +
+
+
+
+ Término +
+
+ dd/mm/aaaa +
+
+ --▾ +
+
+ --▾ +
+
+
+
+
+ Assessores responsáveis +
+
+
+
+ Adicionar +
+
+
+
+ Organizações apoiadoras +
+
+
+
+
+
+
+
+
+
+ Cancelar +
+
+ Salvar atividade +
+
+
+
+
+
+ + +
+

+ Cena 2 · Desktop — modal de sincronização Google (feed ICS segue o mesmo padrão) +

+

+ Corpo com altura máxima e rolagem interna: a lista longa de opções não vaza para fora da + tela; os botões ficam fixos no rodapé. +

+
+
+
+
+

Sincronizar com Google Agenda

+ × +
+
+

+ Escolha o que importar e o que publicar no feed da agenda. As opções continuam + acessíveis rolando dentro do modal. +

+
+
+ +
+

Importar eventos do Google

+
+
+
+
+ +
+

Publicar atividades no Google Agenda

+
+
+
+
+ +
+

Selecionar quais calendários sincronizar

+
+
+
+
+ +
+

Avisos de conflito de horário

+
+
+
+
+ +
+

Fuso horário dos eventos

+
+
+
+
+ +
+

Reprocessar sincronização anterior

+
+
+
+
+
+
+
+ Fechar +
+
+ Sincronizar agora +
+
+
+
+
+
+ + +
+

+ Cena 3 · Mobile ~390px — o mesmo modal de criação (não muda) +

+

+ Hoje o mobile já abre como folha (Drawer) com corpo rolável e campos empilhados; a + correção desktop não pode alterar isso. +

+
+ +
+
+
+

Nova atividade

+ × +
+
+
+
+ Título +
+ Título da atividade +
+
+
+ Tipo +
+ Selecione o tipo▾ +
+
+
+ Município +
+ Selecione o município▾ +
+
+
+ Início +
+ dd/mm/aaaa +
+
+
+ Hora▾ +
+
+ Min▾ +
+
+
+
+ Término +
+ dd/mm/aaaa +
+
+
+ Hora▾ +
+
+ Min▾ +
+
+
+
+ Assessores responsáveis +
+
+
+
+
+
+
+
+
+ Cancelar +
+
+ Salvar +
+
+
+
+
+
+ + diff --git a/docs/plans/c148-modais-agenda-desktop.md b/docs/plans/c148-modais-agenda-desktop.md new file mode 100644 index 00000000..385067e5 --- /dev/null +++ b/docs/plans/c148-modais-agenda-desktop.md @@ -0,0 +1,93 @@ +# Agenda: modais usáveis no desktop (criação de atividade, sync e feed) + +Status: rascunho +Atualizado em: 2026-08-27 +Issue: #933 +Priority: P2 +Model: composer-2.5 +Impeccable: B — encaixe nos modais da agenda +Rascunho UI: docs/plans/c148-modais-agenda-desktop-ui-draft.html +Appetite: ~0,5–1 dia eng; um outcome verificável — no desktop, os três modais da agenda abrem com largura/padding coerentes, conteúdo inteiro acessível (scroll quando precisa) e o par data/hora não empilha +Responsável: — + +## Intenção + +Dois pedidos fundidos sobre `/campanha/atividades` no desktop. No modal de criação de atividade: "o layout no desktop está esquisito — bordas gigantes, modal desnecessariamente estreito, e o seletor de data/hora fica um em cima do outro". No modal de sincronização Google: "o modal é estreito demais e o conteúdo passa do topo e da base da tela, sem rolagem para ver o resto". É a mesma dor — modais da agenda que não respeitam a janela do desktop — e o modal de feed ICS carrega o mesmo defeito latente do sync. Correção de encaixe, não redesign. + +## Persona e fluxo + +- **Persona / contexto:** assessor (ou coordenador) na mesa, no desktop, montando a agenda da semana e sincronizando o Google Calendar; tarefa operacional curta e repetida. +- **Job principal:** criar/editar uma atividade e configurar a sincronização sem lutar contra o modal. +- **Fluxo desejado:** + 1. Abre "Nova atividade" → modal abre com largura generosa e paddings contidos (nada de "bordas gigantes"). + 2. Em Início/Término, os dois campos ficam lado a lado, cada um com data e hora na mesma linha. + 3. Se o conteúdo excede a altura da janela, o corpo rola internamente — os botões de ação ficam sempre visíveis no rodapé. + 4. Abre o modal de sync Google → a lista de opções rola no corpo, footer fixo. + 5. Abre o modal de feed ICS → mesmo comportamento de corpo rolável. + 6. No mobile, nada muda (folha com rolagem de hoje). +- **Anti-goals de produto:** redesign de formulário/campos, mudança de lógica (validação/salvamento) dos modais, padronização de outros modais do app, feature nova. + +### Esboço de fluxo (B/C/D) + +```text +[/campanha/atividades] → [Nova atividade · modal desktop] → preencher → Início/Término lado a lado → [Salvar] + → [Sync Google · modal] → corpo com rolagem + footer fixo → [Sincronizar] + → [Feed ICS · modal] → corpo com rolagem + footer fixo → [Copiar link] +``` + +### Rascunho UI (B/C/D) + +- Rascunho UI (gate): `docs/plans/c148-modais-agenda-desktop-ui-draft.html` — cena 1 (criação em desktop ~1280px), cena 2 (sync Google com corpo rolável e footer fixo; feed ICS segue o mesmo padrão), cena 3 (a mesma criação em ~390px, mobile inalterado). + +## Objetivo e aceite + +- No desktop, os TRÊS modais da agenda (criação/edição de atividade, sync Google, feed ICS) abrem com largura e padding coerentes — sem bordas gigantes, sem largura espremida. +- Nenhum dos três corta conteúdo: quando o corpo excede a altura disponível, há rolagem interna e o rodapé de ações permanece visível. +- O par Início/Término do modal de criação não empilha no desktop — data e hora de cada campo na mesma linha (o rascunho mostra a disposição recomendada). +- Guardrails: mobile dos três modais intocado (apenas não pode regredir); lógica de validação/salvamento e dados intocados; nenhum outro modal do app muda. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — correção de layout/encaixe; nenhuma superfície de dados nova. +- **Decisões desbloqueadas:** nenhuma além do aceite visual do gate. + +## Dados da decisão (literais) + +- N/A — a intenção não fixa IDs, strings ou thresholds; os literais operacionais são os três modais nomeados na direção e o corte desktop vs mobile (mobile = comportamento atual da folha com rolagem). + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `src/components/campaign/activity/` — `ActivityOverlay.tsx` (criação/edição), `GoogleCalendarSyncDialog.tsx`, `CalendarFeedDialog.tsx`, `ActivityDateTimeField.tsx`; base compartilhada `src/components/ui/dialog.tsx`. +- **Hipótese central:** o override desktop do overlay usa largura/padding não-responsivos (`max-w-2xl`, `p-0`) que NÃO derrubam as variantes `sm:` da base (`sm:max-w-md`, `sm:p-8`) — em telas ≥ sm vencem as da base, e o modal fica com 28rem de largura e 2rem de padding (o "estreito + bordas gigantes"). Nos dois dialogs menores falta `max-h` + rolagem no caminho desktop (o mobile já resolve com a folha). O empilhamento data/hora vem da caixa estreita + grade de duas colunas. +- **Precedente a olhar:** o padrão mobile do próprio overlay (folha com max-h e corpo rolável) e do sync/feed no mobile — é o padrão a replicar no desktop. +- **Risco de acoplamento:** `ui/dialog.tsx` é a base de todos os modais do app — mudá-la globalmente causaria regressão silenciosa em toda tela; preferir correção local nos três modais da agenda (override explícito por breakpoint), sem tocar em outros consumidores. + +## Dependências + +- Nenhuma dura. Soft: C123 (ActivityOverlay — superfície de criação atual) e C94 (feed ICS) como precedentes de onde o defeito vive. + +## Fora de escopo + +- Mobile dos três modais (já está bom) — apenas garantir que não regrida. +- Lógica dos modais: validação, salvamento, dados e estados de erro intocados. +- Redesenho do formulário de atividade (campos, defaults, seções) — outro dia, outro pedido. +- Padronização de outros modais do app fora da agenda. +- A funcionalidade de sync/feed em si (feature existente — só o encaixe). + +## Rabbit holes de produto + +- **"Aproveitar e padronizar todos os modais do app."** Se alguém "só completar": varredura de todas as telas, PR gigante, regressão em massa. **Corte neste item:** só os três modais da agenda. +- **"Redesenhar o formulário de atividade."** Se alguém "só completar": reorganizar seções, campos novos, defaults. **Corte:** só encaixe/medidas dos campos existentes. +- **"Mudar a base ui/dialog.tsx para resolver de uma vez."** Se alguém "só completar": mudança global sem varredura de consumidores. **Corte:** mudança local nos três modais; a base só muda se o executor varrer os consumidores e provar zero regressão. + +## Questões em aberto (produto) + +- **Como dispor Início/Término no desktop?** **Opções:** A) os dois campos lado a lado, cada um com data+hora inline | B) um campo por linha com data+hora na mesma linha. **Recomendação:** A no desktop (aproveita a largura recuperada), fallback B se apertado. _(assumido — validar no gate com o rascunho; a cena 1 mostra A)._ +- **Largura dos modais de sync/feed:** manter a atual (~32rem) ou ampliar um pouco (~36rem)? **Recomendação:** ampliar pouco e garantir rolagem — o defeito principal do sync/feed é a falta de scroll, não a largura. _(assumido — a cena 2 mostra ~36rem)._ + +## Referências + +- GitHub Issue: — (após `pnpm agent:register`) +- Rascunho UI (gate): `docs/plans/c148-modais-agenda-desktop-ui-draft.html` +- Arquivos para abrir primeiro: `src/components/campaign/activity/ActivityOverlay.tsx`, `GoogleCalendarSyncDialog.tsx`, `CalendarFeedDialog.tsx`, `ActivityDateTimeField.tsx`, `src/components/ui/dialog.tsx` +- `AGENTS-campaign.md` — seção "Campaign activities (C3)" (vocabulário Atividade; dado interno de staff, sem LGPD/consent aqui) diff --git a/docs/plans/c149-conectar-google-oauth-ui-draft.html b/docs/plans/c149-conectar-google-oauth-ui-draft.html new file mode 100644 index 00000000..05d8aba3 --- /dev/null +++ b/docs/plans/c149-conectar-google-oauth-ui-draft.html @@ -0,0 +1,178 @@ + + + + + + + UI draft — C149 Conectar Google (OAuth) + + +
+

+ Candidato/coordenador configura o espelho Google Calendar da agenda — conexão por botão, sem + service account. Cenas: não configurado, conectado, erro de token, mobile. +

+ +
+

+ Cena 1 · Desktop — estado: não configurado +

+
+
+
+

Google Calendar

+

+ Espelha a agenda da campanha num calendário Google, com atualização em minutos. +

+
+ + Não configurado + +
+
+

+ Conecte a conta Google do calendário da campanha para ativar o espelho. +

+

+ Você será levado à tela de consentimento do Google para autorizar o Teqo a gerenciar o + calendário da campanha. +

+ +
+

+ A configuração manual por service account continua disponível como caminho técnico de + fallback. +

+
+
+ +
+

+ Cena 2 · Desktop — estado: conectado +

+
+
+
+

Google Calendar

+

+ O espelho usa a conexão abaixo para escrever os compromissos no calendário da + campanha. +

+
+ + Conectado + +
+
+
+
+
+

Conta Google da campanha

+
+
+
+
+ + +
+
+

+ Desconectar revoga o acesso do Teqo; depois, revogue o app nas configurações de + segurança da conta Google. +

+
+
+ +
+

+ Cena 3 · Desktop — estado: erro de token (caminho de reconexão) +

+
+
+
+

Google Calendar

+

+ O espelho está pausado até a conexão ser restabelecida. +

+
+ + Erro + +
+
+

Erro de conexão com o Google

+

+ O token de acesso não pôde ser renovado. O Teqo continua sendo a fonte da verdade — a + agenda segue funcionando aqui; nada foi perdido no espelho. +

+

+ Reconecte com a mesma conta Google do calendário da campanha. Se a conta mudou, + desconecte antes e conecte novamente. +

+
+ + +
+
+
+
+ +
+

+ Cena 4 · Mobile ~390px — estado: não configurado (cena 1) +

+
+
+
+
+

Google Calendar

+

+ Espelha a agenda da campanha num calendário Google. +

+
+ + Não configurado + +
+
+

+ Conecte a conta Google do calendário da campanha para ativar o espelho. +

+

+ Você autoriza o Teqo na tela de consentimento do Google. +

+ +
+

+ Service account segue disponível como fallback técnico. +

+
+
+
+
+ + diff --git a/docs/plans/c149-conectar-google-oauth.md b/docs/plans/c149-conectar-google-oauth.md new file mode 100644 index 00000000..f17cfd84 --- /dev/null +++ b/docs/plans/c149-conectar-google-oauth.md @@ -0,0 +1,91 @@ +# C149 — Google Calendar: botão Conectar Google (OAuth) substitui a configuração manual de service account + +Status: rascunho +Atualizado em: 2026-08-27 +Issue: #934 +Priority: P2 +Model: composer-2.5 +Depends: #932 (C147) +Impeccable: B — encaixe na agenda (configuração do sync) +Rascunho UI: docs/plans/c149-conectar-google-oauth-ui-draft.html +Appetite: ~2 dias eng; um outcome verificável — o candidato/coordenador conecta a conta Google do calendário da campanha clicando um botão (consent do Google), sem criar service account nem colar JSON +Responsável: — + +## Intenção + +Ativar o espelho Google Calendar (C114) hoje exige cerimônia de engenharia: criar service account no GCP, baixar JSON, base64, colar em env, compartilhar o calendário com o e-mail da conta de serviço. O dono do produto: _"This manual configuration of the service account plus calendar isn't ideal — isn't there a way to expose a button that allows the candidate to sync a calendar to it?"_ O registro C114 recusou OAuth **por pessoa** (N staff escrevendo no calendário pessoal de cada um). O pedido atual é diferente: **uma** concessão OAuth em que candidato/coordenador autoriza o Teqo a gerenciar **o calendário da campanha** — variante não coberta pelo registro, vinda como nova evidência do dono. Configuração vira um clique. + +## Persona e fluxo + +- **Persona / contexto:** candidato ou coordenador geral, no computador (mesa), configurando o espelho da agenda uma única vez — e voltando só quando algo quebra. +- **Job principal:** "conecto a conta Google do calendário da campanha ao Teqo sem ajuda de engenharia". +- **Fluxo desejado:** na configuração do sync da agenda → clica "Conectar com o Google" → tela de consent do Google (escolhe a conta da campanha) → volta ao Teqo com estado "conectado" → o sync passa a usar essa conexão; se o token quebrar um dia, o estado mostra "erro" com caminho de reconexão (clicar de novo). +- **Anti-goals de produto:** não é OAuth por pessoa (C114 continua valendo — UMA concessão, calendário da campanha); não é migração obrigatória (service account segue funcionando como fallback); não mexe na seleção de qual calendário (C150) nem no motor de reconciliação; leader não vê nem conecta (lockdown). + +### Esboço de fluxo (B) + +```text +[/campanha/agenda — configuração do sync] → "Conectar com o Google" → consent Google (conta da campanha) +→ callback de volta ao Teqo → estado "conectado" → sync usa a conexão (service account vira fallback) +``` + +### Rascunho UI (gate) + +- Rascunho UI (gate): `docs/plans/c149-conectar-google-oauth-ui-draft.html` — cenas: não configurado (botão), conectado, erro de token com reconexão, mobile. + +## Objetivo e aceite + +- O candidato/coordenador conecta a conta Google do calendário da campanha clicando um botão (consent do Google) — sem criar service account, sem colar JSON, sem tocar em GCP. +- Estado da conexão visível e honesto: conectado | erro | não configurado; erro de token mostra caminho de reconexão em um clique. +- Falha de token/Google não corrompe dados: Teqo continua SoT; o espelho degrada para "erro", nada é apagado nem duplicado. +- Funciona atrás do túnel Cloudflare: o callback OAuth usa a URL pública correta (nada de localhost vazando no redirect). +- Guardrails: o token nunca aparece em log; armazenamento fail-closed (sem conexão → sem sync, sem crash); desconectar revoga o acesso no Teqo e orienta revogar no Google; leader lockdown intocado. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — a conexão é infraestrutura do espelho (saída); nenhuma leitura nova de dados no Teqo. O `calendarList` (escopo readonly) alimenta apenas a escolha de calendário do C150. +- **Decisões desbloqueadas:** candidato/coordenador decide (uma vez) qual conta Google autoriza; diante de "erro", o mesmo ator decide reconectar ou desconectar. +- **Forma:** _adiada ao plano de implementação_ — aqui só as restrições: estado ternário, nunca exibir o token, sem inventar estados intermediários. + +## Dados da decisão (literais) + +- Escopos OAuth mínimos: `https://www.googleapis.com/auth/calendar.events` + `https://www.googleapis.com/auth/calendar.calendarlist.readonly` (o segundo basta para listar calendários no C150 — sem escopo além do necessário). +- Estados da conexão (UI + registro): `conectado | erro | não configurado` — exatamente esses três. +- Env de credencial que vira fallback (não renomear nem remover nesta entrega): `GOOGLE_CALENDAR_SERVICE_ACCOUNT_KEY`. +- Restrição dura de produto: o token DEVE sobreviver sem re-consentimento semanal (o caminho técnico — app unverified vs verificação Google — fica em aberto, ver Questões). + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** configuração do sync na agenda `/campanha/agenda` (botão/estado), rotas de callback OAuth em `src/app/(campaign)/campanha/…`, utilitários Google perto de `googleCalendarSync.ts`/`googleCalendarClient.ts`, armazenamento da conexão na collection `googleCalendarSync` (onde `calendarId` já vive). +- **Precedente a olhar:** plano C114 (decisão "não OAuth por pessoa"); par `readGoogleServiceAccountCredentials`/`googleCalendarClient` (REST hand-rolled com `jose`, sem lib Google hoje); WebAuthn de campaignUser (precedente de cerimônia de credencial com rota fora de `(app)`). +- **Risco de acoplamento:** o token é credencial da conta Google da campanha — nunca logar, fail-closed, desconectar revoga; leader lockdown; C147 merged antes (o sync funcionando/diagnosticado) para não brigar no mesmo client. + +## Dependências + +- **Dura: C147** — o sync precisa estar funcionando/diagnosticado antes de trocar o modelo de credencial (também evita conflito de arquivos no client). + +## Fora de escopo + +- Escolha/listagem de qual calendário conectar → **C150** (aqui só entrega a conexão e o estado). +- Migração/remoção obrigatória da service account (fica como fallback; aposentar é entrega futura). +- OAuth por pessoa (anti-goal C114, reafirmado) e leitura de agenda pessoal para conflito de horário (item separado, se um dia existir). +- Motor de reconciliação do sync (janela, locks, hooks C114/C122) — intocado. + +## Rabbit holes de produto + +- **"Verificação completa do app no Google."** Se alguém "só completar": auditoria, casework, semanas de fila — para uso interno. **Corte neste item:** caminho unverified (uso interno, <100 usuários) com aviso no consent; verificação só se o produto um dia precisar. +- **"Desconectar + limpar tudo + migrar quem usa service account."** **Corte:** desconectar = revogar no Teqo + orientação de revogar no Google; ninguém é obrigado a migrar nesta entrega. +- **"Seletor de calendário dentro do botão."** Se alguém "só completar": puxa o C150 para dentro. **Corte:** a conexão autoriza; escolher o calendário é o item seguinte. + +## Questões em aberto (produto) + +- **Manter a service account como fallback depois que o OAuth existir?** **Opções:** A) sim, como caminho técnico até o OAuth estar comprovado em produção | B) aposentar na mesma entrega. **Recomendação:** **A** — aposentar é entrega separada. _(assumido — validar com produto)_ +- **Quem pode conectar/desconectar?** **Opções:** A) candidato + coordenador | B) só candidato | C) também advisor. **Recomendação:** **A** — é a credencial da campanha inteira; advisor não autoriza credencial global, leader lockdown. _(assumido — validar)_ +- **App unverified ou verificação Google?** **Opções:** A) unverified (aviso no consent, token não expira em ~7 dias) | B) verificação completa (sem aviso, custo de auditoria). **Recomendação:** **A** — uso interno, <100 usuários; a restrição dura é o token sobreviver sem re-consentimento semanal. _(trade-off do aviso é a decisão de produto)_ + +## Referências + +- Rascunho UI (gate): `docs/plans/c149-conectar-google-oauth-ui-draft.html` +- Plan C114 (`c114-google-calendar-push-agenda-oficial.md` — decisão "não OAuth por pessoa"); C147 (dependência dura); C150 (seleção de calendário, fora daqui) +- `src/utilities/googleCalendarSync.ts`, `src/utilities/googleCalendarClient.ts`, collection `googleCalendarSync` (`calendarId`) +- `AGENTS-campaign.md` — leader lockdown / papéis de `/campanha` diff --git a/docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html b/docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html new file mode 100644 index 00000000..33c91a25 --- /dev/null +++ b/docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html @@ -0,0 +1,192 @@ + + + + + + + UI draft — C150 Calendário principal e inscrição de staff no Google + + +
+

+ C150 — Agenda do Teqo: o núcleo (candidato/coordenador) escolhe o calendário principal da + campanha; o staff adiciona a agenda ao Google dele com um clique. Líder nunca vê estas + ações. +

+ +
+

+ Cena 1 — desktop · seletor de calendário principal (candidato/coordenador) +

+
+
+
+

Calendário principal da campanha

+

+ Criar e editar atividades espelham no calendário escolhido. +

+
+
+

Calendários da conta Google conectada

+
    +
  • + + + + Calendário da campanha + em uso +
  • +
  • + + Meu calendário +
  • +
  • + + +
  • +
+
+
+ Cancelar + Escolher calendário +
+
+
+
+ +
+

+ Estado — desktop · sem conta Google conectada (depende de C149) +

+
+
+

Calendário principal da campanha

+

+ Conecte a conta Google da campanha para listar e escolher o calendário principal. +

+
+ Conectar conta Google +
+
+
+
+ +
+

+ Cena 2 — desktop · chrome da agenda com o botão de staff +

+
+
+
+

Agenda

+ Agosto de 2026 + Google: sincronizado + Link de import + Adicionar ao meu Google Calendar +
+
+
+ + município +
+
+ + município +
+
+
+

+ O recorte filtrado (município/tags) segue no fluxo atual do feed ICS: copiar link + + instrução "Do URL" — botão "Link de import". +

+
+
+ +
+

+ Estado — desktop · confirmação da inscrição +

+
+
+
+

Agenda da campanha adicionada ao seu Google Calendar.

+

+ Eventos e edições aparecem em minutos, conforme as notificações do seu Google. +

+
+
+
+ + município +
+
+ + município +
+
+
+
+
+ +
+

+ Cena 3 — mobile 390px · botão de staff (caso de uso em campo) +

+
+
+
+
+

Agenda

+ Google: sincronizado +
+
+ Adicionar ao meu Google Calendar + Link de import +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ + diff --git a/docs/plans/c150-calendario-principal-e-inscricao-staff.md b/docs/plans/c150-calendario-principal-e-inscricao-staff.md new file mode 100644 index 00000000..c41a0025 --- /dev/null +++ b/docs/plans/c150-calendario-principal-e-inscricao-staff.md @@ -0,0 +1,100 @@ +# C150 — Agenda: calendário principal escolhido pelo candidato/coordenador + inscrição de staff no Google + +Status: rascunho +Atualizado em: 2026-08-27 +Issue: #935 +Priority: P2 +Model: composer-2.5 +Depends: #934 (C149) +Impeccable: B — encaixe na agenda +Rascunho UI: docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html +Appetite: ~1,5–2 dias eng; um outcome verificável — o candidato/coordenador escolhe o calendário principal da campanha na própria agenda (create/edit espelham nele) e um staff adiciona a agenda ao Google dele com um clique +Responsável: — + +## Intenção + +Dois pedidos fundidos sobre o espelho Google Calendar entregue por C114/C115. Hoje, escolher QUAL calendário Google espelha a agenda exige o admin do Payload — mas quem decide o calendário oficial da campanha é o candidato/coordenador. E inscrever a agenda no Google pessoal de cada staff ainda é manual: copiar o link e configurar "Por URL" à mão — o próprio pedido reconhece que o caminho certo é um botão que leva ao Google já preparado. Na agenda do Teqo: o núcleo escolhe o calendário principal entre os da conta Google conectada (C149) e o staff adiciona a agenda ao Google dele com um clique. + +## Persona e fluxo + +- **Persona / contexto:** candidato/coordenador na mesa decidindo a agenda oficial; assessores e comunicação em campo, vivendo no Google Calendar do celular; líder de campo nunca toca nisso. +- **Job principal:** "escolho qual calendário da nossa conta Google é a agenda oficial — e a agenda da campanha aparece no meu Google Calendar sem eu configurar nada à mão". +- **Fluxo desejado:** + 1. Na agenda (`/campanha/agenda`), o candidato/coordenador abre a configuração do Google e vê os calendários da conta conectada. + 2. Escolhe um como calendário principal e confirma; criar/editar atividades passam a espelhar nele. + 3. Um staff (coordenador/assessor/candidato) clica "Adicionar ao meu Google Calendar": o Google abre com o calendário oficial pré-configurado — sem copiar link nem "Por URL". + 4. Para recorte filtrado (município/tags), o caminho continua o feed ICS atual: copiar link + instrução — nada muda de comportamento. +- **Anti-goals de produto:** não escrever no calendário PESSOAL de cada staff (decisão C114); não criar N calendários espelhados — o calendário principal é UM; não refazer o feed ICS nem o motor de sync; sem import de eventos do Google além do que C115 já faz; leader lockdown intocado. + +### Esboço de fluxo (B) + +```text +[agenda /campanha/agenda] → "Calendário principal" (candidato/coordenador) +→ lista os calendários da conta Google conectada (C149) → escolhe → confirma +→ espelho re-concilia no calendário escolhido; create/edit espelham nele + +[mesma agenda, staff] → "Adicionar ao meu Google Calendar" → um clique +→ Google abre com o calendário oficial pré-configurado → agenda no Google pessoal +``` + +### Rascunho UI (B) + +- Rascunho UI (gate): `docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html` + +## Objetivo e aceite + +- Na própria agenda, o candidato/coordenador lista os calendários da conta Google conectada e escolhe o calendário principal — configurar o espelho deixa de exigir o admin do Payload. +- Criar/editar atividades espelham no calendário escolhido — o motor C114/C115 não muda de modelo; trocar o calendário re-concilia o espelho sem eventos órfãos/duplicados visíveis ao staff. +- Um staff adiciona a agenda ao Google pessoal com um clique — sem copiar link nem configurar "Por URL" à mão; o recorte filtrado segue no fluxo atual do feed ICS (link + instrução). +- Guardrails: leader não vê nem usa nenhuma das duas ações; Teqo continua SoT; o calendário principal é UM; falha do Google não derruba a agenda do Teqo (estado do sync continua visível); o admin do Payload continua como escape hatch da configuração. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — o espelho é saída (mesma posição do C114); nenhuma leitura nova no Teqo. +- **Decisões desbloqueadas:** candidato/coordenador escolhe o calendário principal da campanha (ator + escolha, hoje exclusiva do admin do Payload); staff opta por inscrever a agenda no Google pessoal dele (ator + escolha, hoje manual). +- **Forma:** _adiada ao plano de implementação_ — a apresentação do evento no Google já é decisão do C114. + +## Dados da decisão (literais) + +- Padrão do link one-click do calendário oficial: `https://calendar.google.com/calendar/r?cid=` — confiável para calendar IDs de calendários Google reais (caso do calendário oficial da campanha). O builder atual compõe o `cid` com a URL webcal pública do calendário — reconciliar com este literal na implementação. +- Para o recorte ICS, quando um one-click for tentado: `cid` com `webcal://` — INFIEL/instável para feeds ICS externos ("Unable to add calendar", eventos não populam); fallback obrigatório = copiar link + instrução "Do URL" (fluxo atual). Assumido v1: NÃO tentar — só link + instrução. +- Rótulos de placeholder do rascunho UI: "Calendário da campanha" (oficial) e "Meu calendário" (pessoal genérico da conta conectada). + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** chrome da agenda em `src/components/campaign/activity/` (`AgendaGoogleSyncChrome.tsx`, `GoogleCalendarSyncDialog.tsx`, `CalendarFeedDialog.tsx`), ações em `src/app/(campaign)/campanha/actions/googleCalendarSync.ts`, papel em `src/utilities/access/googleCalendarSync.ts` (hoje `canSetGoogleCalendarSyncConfigField = isPayloadAdmin` — é isso que o produto quer abrir ao candidato/coordenador), links em `src/lib/googleCalendarLink.ts`. +- **Precedente a olhar:** plans C114/C115 (espelho e bidirecional — motor não muda de modelo), C16/C113 (feed ICS); trocar `calendarId` já dispara re-conciliação automática (config hook D7; snapshot pino por calendarId; canal push re-criado por calendário) — o delta é a troca de dono da escolha, não a mecânica. +- **Risco de acoplamento:** abrir a escolha a candidato/coordenador muda uma permissão de configuração hoje admin-only (a intenção do produto é exatamente essa); leader lockdown intocado; o admin do Payload permanece funcionando. + +## Dependências + +- **Dura:** C149 — o seletor nasce da conexão OAuth (listar os calendários da conta conectada). +- Suaves: C114/C115 (motor do espelho, em produção), C16/C92/C98/C113 (feed ICS e link de import). + +## Fora de escopo + +- Escrever no calendário pessoal de cada staff via OAuth individual → anti-goal reafirmado da decisão C114. +- N calendários espelhados (um por filtro/papel) → recorte continua no feed ICS (C16/C113). +- Refazer o feed ICS, o motor de sync ou o import bidirecional do C115. +- One-click universal para feeds ICS externos → infiável (pesquisa); o recorte mantém link + instrução. +- Escolher calendário principal por assessor → decisão de núcleo; assessor só inscreve. + +## Rabbit holes de produto + +- **"Um calendário principal por filtro/papel."** Se alguém "só completar": N espelhos divergentes e permissões por calendário. **Corte neste item:** um calendário principal, escolhido pelo núcleo; recorte continua no feed ICS. +- **"One-click para todo tipo de agenda (incl. ICS)."** Se alguém "só completar": botão que falha com "Unable to add calendar" e gera suporte. **Corte:** one-click só para o calendário oficial (Google real); recorte mantém copiar link + instrução. +- **"Sincronizar todo o histórico ao trocar de calendário."** **Corte:** o motor atual governa a janela; a troca re-usa a re-conciliação existente, sem backfill novo. + +## Questões em aberto (produto) + +- **O botão one-click do recorte filtrado (ICS) vale em v1?** **Opções:** A) sim, como tentativa com fallback claro | B) não — só o calendário oficial em v1, recorte segue link+instrução. **Recomendação:** B — o padrão cid é infiel para ICS e a falha custa confiança. _(assumido — validar no gate)_ +- **A escolha do calendário principal substitui completamente a config no admin do Payload?** **Opções:** A) substitui na UI da agenda; admin fica como escape hatch | B) mantém as duas como superfícies de primeira classe. **Recomendação:** A — uma só casa para a decisão; admin permanece para operação/contingência. _(assumido — validar)_ +- **Assessor pode escolher o calendário principal?** **Opções:** A) só candidato/coordenador | B) também assessor. **Recomendação:** A — decisão de núcleo sobre a agenda oficial; assessor apenas inscreve a agenda no Google dele. + +## Referências + +- GitHub Issue: — (após `pnpm agent:register`) +- Rascunho UI (gate): `docs/plans/c150-calendario-principal-e-inscricao-staff-ui-draft.html` +- Plans C114 (`c114-google-calendar-push-agenda-oficial.md`), C115 (`c115-google-calendar-edicao-bidirecional.md`), C16 (`sync-teqo-google-calendar.md`), C113 (`c113-feed-ical-agenda-nao-atualiza.md`) +- Arquivos para abrir primeiro: `src/lib/googleCalendarLink.ts`, `src/components/campaign/activity/AgendaGoogleSyncChrome.tsx`, `src/components/campaign/activity/GoogleCalendarSyncDialog.tsx`, `src/components/campaign/activity/CalendarFeedDialog.tsx`, `src/utilities/access/googleCalendarSync.ts`, `src/collections/GoogleCalendarSync.ts`, `src/utilities/activityUi.ts` +- `AGENTS-campaign.md` — convenção de papéis/lockdown tocada pelo item diff --git a/docs/plans/c151-atividade-evento-publico-ui-draft.html b/docs/plans/c151-atividade-evento-publico-ui-draft.html new file mode 100644 index 00000000..55d4c4d7 --- /dev/null +++ b/docs/plans/c151-atividade-evento-publico-ui-draft.html @@ -0,0 +1,274 @@ + + + + + + + UI draft — Atividade: marcar evento como público (C151) + + +
+

+ UI draft — Atividade: marcar evento como público (C151) +

+

+ Staff edita uma atividade no modal e marca "Evento público" (default desmarcado); a marcação + é durável — nenhuma tela muda de comportamento. +

+

+ Cinza = contexto existente (sem mudança) · acento = novo + neste item. Posição do checkbox segue a recomendação (junto dos outros checkboxes) — validar + no gate. +

+ +
+

+ Cena 1 · desktop — modal de atividade, seções "Informações básicas" e "Agendamento" +

+
+
+
+

Editar atividade

+

/campanha/atividades

+
+ × +
+
+

+ Informações básicas +

+
+
+

Título

+
+ +
+
+
+
+

Tipo

+
+ + ▾ +
+
+
+

Status

+
+ + ▾ +
+
+
+ +
+ +

+ Agendamento +

+
+
+
+

Início

+
+ +
+
+
+

Fim

+
+ +
+
+
+
+ + +
+
+
+
+ Cancelar + Salvar +
+
+
+ +
+

+ Cena 2 · mobile (~390px) — mesma cena +

+
+
+
+

Editar atividade

+

/campanha/atividades

+
+ × +
+
+

+ Informações básicas +

+
+
+

Título

+
+ +
+
+
+
+

Tipo

+
+ + ▾ +
+
+
+

Status

+
+ + ▾ +
+
+
+ +
+ +

+ Agendamento +

+
+
+
+

Início

+
+ +
+
+
+

Fim

+
+ +
+
+
+
+ + +
+
+
+
+ Cancelar + Salvar +
+
+
+ +

+ Rascunho descartável (gate) — registro do aceite visual. Zero JS além do build browser do + Tailwind v4; sem comportamento. +

+
+ + diff --git a/docs/plans/c151-atividade-evento-publico.md b/docs/plans/c151-atividade-evento-publico.md new file mode 100644 index 00000000..e91c1862 --- /dev/null +++ b/docs/plans/c151-atividade-evento-publico.md @@ -0,0 +1,94 @@ +# Atividade: marcar evento como público (base para exposição no site) + +Status: rascunho +Atualizado em: 2026-08-27 +Issue: #936 +Priority: P3 +Model: composer-2.5 +Serializes: #933 (C148) +Impeccable: B — encaixe no modal de atividade +Rascunho UI: docs/plans/c151-atividade-evento-publico-ui-draft.html +Appetite: ~0,5 dia eng; um outcome verificável — o staff marca/desmarca "Evento público" e a marcação é durável e consultável, sem mudar nada no comportamento atual das telas +Responsável: — + +## Intenção + +"Let's add a new property to activities to register it as a public event. Public events will later be exposed in the candidate's website to be shareable for followers/supporters." — hoje uma atividade (caminhada, comício, agenda do mandato) é só registro interno: quando o time quiser divulgar um evento no site, não há como distinguir o que é público do que é operação interna. Este item adiciona essa distinção — uma marcação simples, durável e consultável — para que a futura agenda pública do site tenha um dado confiável de onde partir. A exposição em si é outro item (PUB): aqui só se marca a intenção. + +## Persona e fluxo + +- **Persona / contexto:** staff de campanha (coordenador/assessor/candidato) editando atividades em `/campanha/atividades`, no escritório ou em campo; liderança não entra nesta história. +- **Job principal:** ao cadastrar/editar uma atividade, marcar se ela é um evento público — sem planilha paralela nem precisar lembrar disso depois. +- **Fluxo desejado:** abrir o modal da atividade → marcar/desmarcar "Evento público" (desmarcado por padrão) → salvar; a marcação persiste e volta correta ao reabrir. Nenhuma tela muda de comportamento. +- **Anti-goals de produto:** página pública ou share kit agora; segundo cadastro de "evento" paralelo à atividade; filtro/badge nova na lista; flag alterando o espelho do Google ou o access atual. + +### Esboço de fluxo (B/C/D) + +```text +[início: modal de atividade] → staff marca "Evento público" (default: desmarcado) +→ salvar → [outcome: marcação durável e consultável; nenhuma tela muda de comportamento] +``` + +### Rascunho UI (B/C/D) + +- Rascunho UI (gate): `docs/plans/c151-atividade-evento-publico-ui-draft.html` — cenas desktop + mobile (~390px) do modal com o novo checkbox ao lado dos existentes. + +## Objetivo e aceite + +- O staff que edita atividades marca/desmarca "Evento público"; a marcação persiste entre sessões, volta correta ao reabrir a atividade e fica consultável para o consumidor futuro — sem nenhum comportamento novo visível nas telas. +- **Guardrails de produto:** a flag marca INTENÇÃO de exposição; a exposição é FUTURA (item PUB posterior). Nada no site público muda; nada na agenda muda de comportamento. +- **Guardrail para o FUTURO consumidor:** atividade pública nunca expõe lideranças, tarefas, resultados nem dados operacionais internos — a superfície pública futura mostra o "cartaz do evento" (título, data/hora, município/local, descrição), nunca o backend operacional. +- Quem marca é o mesmo staff que edita atividades (coordenador/assessor/candidato); liderança segue em lockdown e não vê o campo nem nada novo. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — nenhuma superfície de dados neste item; a flag é insumo durável para o item PUB futuro (agenda pública, PUB3 #45, blocked). +- **Decisões desbloqueadas:** staff + escolha de quais atividades são candidatas a exposição pública; o item PUB futuro decide o quê/como expor. +- **Forma:** _adiada ao plano de implementação_ — aqui só a restrição de produto acima (cartaz do evento, nunca backend operacional). + +## Dados da decisão (literais) + +- Rótulo de UI (checkbox): "Evento público" — default desmarcado. +- Descrição admin pt-BR (curta): "Quando marcado, este evento poderá ser publicado no site do candidato (futuro)." +- Quem pode marcar: os mesmos papéis que editam atividades hoje (coordinator/advisor/candidate); liderança não vê o campo. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** collection `activity` (`src/collections/Activity.ts`), modal central `src/components/campaign/activity/` (ActivityOverlay — create+edit), detalhe `/campanha/atividades/[slug]`; Payload admin herda. +- **Precedente a olhar:** checkboxes no mesmo arquivo — `deputyPresent` (~L493) e `allDay` (~L508), ambos checkbox com default desmarcado, indexado e descrição admin; migrations recentes de campo simples (`20260819_213947_add_site_settings_facebook_pixel_id`, `20260823_161905_add_municipality_update_deliberation`) + regeneração de tipos. +- **Risco de acoplamento:** `SYNC_RELEVANT_ACTIVITY_FIELDS` (`googleCalendarSync.ts:100`) decide se campo dispara re-sync — recomendação v1: NÃO incluir (calendário oficial segue full-staff). Access atual (`canReadActivity` inclui leader) NÃO muda; flag de negócio nova não pede field-access especial. + +## Dependências + +- **Serializa com C148** (mesmo modal de atividade — não trabalhar em paralelo). +- PUB3 (#45) — consumidor futuro da flag (agenda pública); está blocked e NÃO é desbloqueado por este item (aqui não se constrói exposição). +- C114 (espelho do Google) — interação a respeitar: decisão v1 = flag não influencia o sync. + +## Fora de escopo + +- Página/agenda pública no site e share kit — item PUB futuro (PUB3 #45); nada no `(frontend)` muda. +- Qualquer consumo/exposição da flag agora (site, feed, filtro, badge na lista). +- Collection nova ou modelagem paralela de "evento público" — a marcação vive na atividade existente. +- Mudança de access/visibilidade (liderança continua sem ver nada novo; Payload admin segue como está). +- Integração da flag com o espelho do Google (decisão v1: não influencia). +- Marcação em lote de atividades existentes (spreadsheet mode — sem pedido). + +## Rabbit holes de produto + +- **"Já deixar a página pública pronta."** Se alguém "só completar": agenda pública, SEO, share kit — explosão de escopo. **Corte neste item:** só a marcação; exposição é item PUB futuro. +- **"Aproveitar e ligar a flag ao espelho do Google."** Se alguém "só completar": re-escopo do calendário oficial (full-staff) e conflito com C114. **Corte neste item:** sync intocado em v1. +- **"Criar collection 'evento público' separada."** Se alguém "só completar": segundo cadastro de atividade, dados divergentes. **Corte neste item:** flag na atividade existente. +- **"Filtrar/badgear a lista por eventos públicos."** Se alguém "só completar": redesign de tabs/filtros da agenda. **Corte neste item:** nenhuma tela muda de comportamento. + +## Questões em aberto (produto) + +- **Em qual seção do modal entra o checkbox?** **Opções:** A) junto dos outros checkboxes do modal (seção básica/agendamento) | B) seção própria "Publicação" | C) campo avançado/oculto. **Recomendação:** A — um checkbox a mais não pede seção nova; o rascunho o mostra ao lado dos existentes. _(assumido — validar no gate com o rascunho)_ +- **A flag influencia o espelho do Google?** **Opções:** A) NÃO em v1 — calendário oficial continua full-staff; a flag existe para o site futuro | B) sim, re-scope do sync. **Recomendação:** A — mudar o sync agora complica C114 sem ganho de produto hoje. _(recomendação — validar com produto)_ + +## Referências + +- GitHub Issue: — (após `pnpm agent:register`) +- Rascunho UI (gate): `docs/plans/c151-atividade-evento-publico-ui-draft.html` +- `src/collections/Activity.ts` (precedentes `deputyPresent`/`allDay`) · `src/components/campaign/activity/ActivityOverlay.tsx` +- `src/utilities/access/activities.ts` · `src/utilities/googleCalendarSync.ts:100` · `src/collections/Post.ts:78` (precedente público mais próximo: `Post.type` valor `evento`) +- `AGENTS-campaign.md` — seção "Campaign activities (C3)" · PUB3 (#45)