From 0acf29a6386f0689b9f7822e079d76106ff3451f Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Mon, 10 Aug 2026 07:55:37 -0300 Subject: [PATCH 1/2] docs(D8): restaura entradas C102/C108 perdidas do CHANGELOG e limpa lixo do rebase do C106 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - C102 (Issue #498) restaurada verbatim de 8c79eddf na posição canônica (entre C104 e B179) - C108 (Issue #522) restaurada verbatim de c3f10cce no topo da seção - B183 (Issue #501) já havia sido re-adicionada pela branch da C106 (7eaa65ce, PR #555) — verificada byte-idêntica, sem ação - Removidos: D6 duplicado (no lugar da C102), C106 v1 (rascunho pré-rebase) e 2 linhas de git log vazadas - Impl: docs/plans/restaurar-entradas-changelog-b183-c102-impl.md --- docs/CHANGELOG-AGENTS.md | 11 ++- ...aurar-entradas-changelog-b183-c102-impl.md | 83 +++++++++++++++++++ 2 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 docs/plans/restaurar-entradas-changelog-b183-c102-impl.md diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index f5e3a7c8..e74aef88 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -3,26 +3,25 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agentes paralelos): "Known Gaps" e todos os blocos "Recently resolved". Histórico — leia só quando precisar do contexto de uma entrega passada; o sempre-presente fica no `AGENTS.md`. Itens novos de "Recently resolved" passam a ser registrados aqui (uma entrada curta), não no AGENTS.md. --- - **Recently resolved (2026-08-10):** **OPS21** (Issue #538) — `REVALIDATE_SECRET` adicionada na **Vercel production** (projeto `solla/jorgesolla`): diagnóstico D6 confirmado (env ausente em `vercel env ls production`; `POST /api/revalidate` respondia `500` "not configured" em prod, quebrando o runbook pós-seed/mudança direta de DB). Segredo gerado com `openssl rand -hex 32` (valor via stdin, nunca argv), `vercel env add REVALIDATE_SECRET production`; **fallback necessário:** o deploy existente congelava as envs em runtime → `vercel redeploy` do último deploy de produção (2×, pois o primeiro `env add` via pipe herdou o `\n` do `openssl` e gravou valor de 66 chars — removido e re-adicionado limpo). Validação real em prod: sem secret → `401`; secret errado → `401`; secret certo → `200 { revalidated: true, tag: 'posts' }`; tag desconhecida → `400`. Preview não adicionada (projeto sem Git conectado — `vercel env add ... preview` exige branch, precedente D6). Sem código/migration/access/UI. Impl [`revalidate-secret-vercel-production-impl.md`](plans/revalidate-secret-vercel-production-impl.md). +**Recently resolved (2026-08-10):** **C108** (Issue #522) — sino de notificações: **abrir já lê** — `openCampaignNotifications` (action única) lista + marca tudo como lido em 1 round trip, substituindo `listCampaignNotifications` + `markAllCampaignNotificationsRead`; o badge zera na abertura sem clique e o botão "Marcar todas como lidas" deixa de existir. Painel limpo em qualquer tamanho: sem título "Notificações" visível (sr-only para a11y), sem contador, sem botão "Fechar"; desktop vira **modal central** (Dialog shadcn, X no canto superior direito, clique fora fecha — par responsivo Dialog/Drawer do C94 via `useIsMobile`); mobile segue bottom sheet com handle, safe-area e **skeleton de loading** (o painel não abre mais em branco durante o round trip). `markAllNotificationsRead` extraída para `src/utilities/notification/notificationList.ts` (owner-scoped com bypass documentado, idempotente, cap 200 mantido; int testado: marca só as próprias, deixa as do outro intactas); `readAt` saiu do contrato `NotificationListItem` (sem leitores após a remoção do destaque de não lida); copy de erro unificada em `CAMPAIGN_NOTIFICATION_LOAD_ERROR_MESSAGE`; `router.refresh()` re-sincroniza a instância irmã do outro header só quando algo foi marcado (race cross-tab documentado); foco visível nos itens e `role="alert"` no erro. P4-D atualizado no ledger — o batch sem transação agora dispara a cada abertura (self-healing). Testes: int (owner-scope + idempotência) e unit novos do bell (dialog limpo, refresh condicional, empty/error, sheet mobile). Sem migration, access, Consent ou schema. Intenção [`sino-notificacoes-leitura-auto-modal-desktop.md`](plans/sino-notificacoes-leitura-auto-modal-desktop.md); impl [`sino-notificacoes-leitura-auto-modal-desktop-impl.md`](plans/sino-notificacoes-leitura-auto-modal-desktop-impl.md). + **Recently resolved (2026-08-10):** **B184** (Issue #514) — no mobile (<768px), o filtro de listas `/campanha` virou o **padrão sem moldura do chassis `CampaignListOmnibox` (B127) — todas as listas**: label oculta (`input` carrega o nome via `aria-label`), campo sem borda/sombra (`md:` restaura o desktop), **form do filtro sticky sob a top bar** com linha separadora edge-to-edge, "Limpar" vira **X circular dentro do input** (aparece com chips OU busca digitada; slot sempre renderizado com `invisible` para a largura não pular; hit target 44px + círculo visual 28px; live region própria "Busca e filtros limpos." — limpar só busca não navega e o pending region ficaria mudo para AT), botão texto "Limpar" desktop-only. **Por que sticky no `
`:** sticky não escapa do containing block (o pai) — a coluna do input dentro do wrapper curto do omnibox "grudava" ~12px e rolava junto (medido); o form é o único wrapper filho do `CampaignPageShell` (altura total). **Exceção agenda (C101, merge anterior):** a `/campanha/agenda` já tinha o strip sticky próprio (`activity-agenda-filter-strip`) — mantido; o chassis ainda a cobre no campo/X/Limpar. Em municípios, adicionalmente: **"Salvar filtro" vira icon button no header mobile** (slot C94/C95, `SaveMunicipalityFilterControl presentation='icon'`; popover de nomear intacto; nome de rename é o contrato 2.5.3) e **cards edge-to-edge sem borda** com uma linha separando (`border-b last:border-b-0`, `-mx-4`, gate `md:`). "Nova atualização" (atualizações) — único trailing visível no mobile — foi para o header (mesmo padrão); `campaignMobileHeaderIconClassName` extraída (3º consumidor). E2E novo em `campaignMunicipalities.e2e.spec.ts` (390×844): label oculta, borda 0, sticky, X (limpa chips+busca, foco volta sem reabrir popover), salvar→renomear no header, cards com separador, desktop invertido (1280). Débito registrado no caminho: **D7** (sticky dos headers da tabela desktop morto — wrapper `overflow-x-auto` é o scroll container; medido). Sem migration, access, Consent. Intenção [`municipios-mobile-sem-moldura.md`](plans/municipios-mobile-sem-moldura.md); impl [`municipios-mobile-sem-moldura-impl.md`](plans/municipios-mobile-sem-moldura-impl.md). **Recently resolved (2026-08-09):** **C103** (Issue #504) — o formulário de criação rápida da agenda ficou **usável de ponta a ponta no celular**: no narrow (mesmo breakpoint 640px do calendário), o sheet de criação abre **do topo** (`swipeDirection="up"` na primitiva `Drawer` — que já suportava top sheet; zero mudança em `Drawer.tsx`) preenchendo `100dvh-1rem`, o conteúdo **rola** num scroll container e o rodapé ("Mais detalhes" + "Salvar") é **fixo** — verificado em smoke com teclado aberto (viewport 280px: sheet encolhe via `dvh`, conteúdo rola 187/187px e o Salvar permanece visível). O form ganhou `variant: 'popover' | 'sheet'`: no sheet, **labels viram `sr-only`** (a11y e `getByLabelText` intactos), placeholders dão contexto ("Adicionar título _", "Município _", "Local (opcional)", "Adicionar responsáveis"), obrigatórios marcados com asterisco (título/município no placeholder, Início no trigger), campos empilhados **sem gaps com `divide-y`** e inputs/triggers sem borda (lista iOS do canvas). O **seletor de data/hora abre como bottom sheet aninhado** no mobile (`ActivityDateTimeField` ganhou `isNarrow`/`label`/`required`: conteúdo compartilhado Calendar+selects renderizado em `Drawer` com header "Início — " + "Pronto"; desktop continua popover). Props backward-compatible: `placeholder`/`className` no `StrictCombobox`, `labelClassName`/`emptyText`/`triggerClassName` no `ResponsibleMultiSelect`. Decisão do smoke: swipe handle **removido** do top sheet (primitiva o posiciona no rodapé, contradizendo o gesto de swipe-up; continua nos bottom sheets). E2E mobile novo (viewport 390×844): sheet do topo (`y ≤ 1`), Salvar visível sem scroll, picker em bottom sheet com Pronto, evento aparece após salvar; unit narrow cobre placeholders/asterisco/Pronto. Desktop visualmente inalterado (B181 segue na fila para o clip do popover ≤720px). Sem migration/access/Consent/schema. Intenção [`agenda-mobile-form-criacao-usavel.md`](plans/agenda-mobile-form-criacao-usavel.md); impl [`agenda-mobile-form-criacao-usavel-impl.md`](plans/agenda-mobile-form-criacao-usavel-impl.md). -**Recently resolved (2026-08-09):** **D6** (Issue #521) — push no device fechado de ponta a ponta. **Diagnóstico com evidência por elo** (leitura read-only de prod): (1) **envs VAPID ausentes no Vercel production** (verificado com `vercel env ls production`) — elo raiz: sem elas o servidor nunca chama o web-push e o card de perfil desabilita "Ativar"; (2) consentimento `campanha-notificacoes-push` já publicado (Onda 0, 2026-07-19); (3) 0 inscrições `pushSubscription` (bloqueado pelo elo 1); (4) 74 `Notification` geradas no sino (eventos fluem) mas **zero envios**; (5) banner no OS = passo humano (Android). **Dois defeitos de código corrigidos com evidência:** (a) o SW `/campanha/sw.js` servia `CACHE_NAME "campanha-dev"` em prod — deploys são CLI `vercel build --prebuilt` (git builds ignorados), e a rota `force-static` congelava o buildId no build onde `VERCEL_GIT_COMMIT_SHA`/`VERCEL_DEPLOYMENT_ID` não existem; a rota virou **`force-dynamic`** (lê `VERCEL_DEPLOYMENT_ID`, garantido em runtime) → cache versionado por deploy (unit test pina `dynamic`); (b) **subscribe não idempotente** — segundo toque em "Ativar neste dispositivo" no mesmo device devolvia a mesma subscription do browser e estourava o UNIQUE do `endpoint` (erro falso com o device já ativo); `subscribeCampaignPush` agora faz find-before-create e responde sucesso. **Ferramenta nova:** `pnpm db:check:push-chain` (`scripts/check-push-chain.mjs`, read-only — `PROD_DATABASE_URL` obrigatória, conexões com `default_transaction_read_only`) fecha um veredito por elo (SW servido/versionado, envs, consentimento, inscrições por usuário, log de eventos) com exit code. **Ops:** par VAPID gerado e adicionado em **Production** (`VAPID_PUBLIC_KEY`/`VAPID_PRIVATE_KEY`/`VAPID_SUBJECT`/`NEXT_PUBLIC_VAPID_PUBLIC_KEY`); preview ficou de fora — o projeto não tem Git repo conectado (deploys CLI) e `vercel env add ... preview` exige branch (`branch_not_found`); `NEXT_PUBLIC_*` precisa do build do próximo deploy para inliner. \*\*Pe... -**Recently resolved (2026-08-09):** **C106** (Issue #517) — feed de atualizações `/campanha/atualizacoes` sem moldura no mobile: label "Filtrar atualizações" vira sr-only (continua no a11y tree), o filtro perde caixa/borda e fica **sticky** sob a top bar com a linha separadora (`border-bottom`), cards edge-to-edge (`-mx-4`) sem borda/arredondamento com divisor de linha. **Mecanismo herdado do C101** (CSS de página, não prop no chassis): `CampaignUpdatesFeed.css` com media query + `:has()` zerando o padding-top do scroll container (padrão `activity-agenda-filter-strip`); o chassis B127 ganhou só um hook estável `data-slot="campaign-omnibox-field"` (sem mudança de comportamento, reutilizável pelo B184). "Nova atualização" (texto+outline) some do mobile e vira **icon no header via `SetCampaignHeaderAction`** (slot C94/C95; `md:hidden` + botão `hidden md:inline-flex` — gate de resolução 100% CSS, mesmo modal C89). Empty state: copy única "Ajuste o filtro ou registre um novo fato de campo." (descreve a ação, vale nas duas resoluções). **Bug pré-existente corrigido no caminho:** o `@layer base` editorial aplica `my-6 ml-6 list-disc [&>li]:mt-2` a todo `ul` — o feed era a única lista campaign em `
    ` (indent de 24px + 8px por li; precedente do patch: `[data-theme='campaign'] p { margin-top: 0 }`); a lista neutraliza na própria classe. **Achado registrado:** páginas dinâmicas (searchParams awaitado) têm um nó `#S:0` hidden **transitório** durante o streaming (cópia da página; some em ~1–5s; só no build de produção) — os e2e aguardam o stream assentar (`settleStream`). Sem migration/access/Consent. Intenção [`atualizacoes-mobile-sem-moldura.md`](plans/atualizacoes-mobile-sem-moldura.md); impl [`atualizacoes-mobile-sem-moldura-impl.md`](plans/atualizacoes-mobile-sem-moldura-impl.md). +**Recently resolved (2026-08-09):** **D6** (Issue #521) — push no device fechado de ponta a ponta. **Diagnóstico com evidência por elo** (leitura read-only de prod): (1) **envs VAPID ausentes no Vercel production** (verificado com `vercel env ls production`) — elo raiz: sem elas o servidor nunca chama o web-push e o card de perfil desabilita "Ativar"; (2) consentimento `campanha-notificacoes-push` já publicado (Onda 0, 2026-07-19); (3) 0 inscrições `pushSubscription` (bloqueado pelo elo 1); (4) 74 `Notification` geradas no sino (eventos fluem) mas **zero envios**; (5) banner no OS = passo humano (Android). **Dois defeitos de código corrigidos com evidência:** (a) o SW `/campanha/sw.js` servia `CACHE_NAME "campanha-dev"` em prod — deploys são CLI `vercel build --prebuilt` (git builds ignorados), e a rota `force-static` congelava o buildId no build onde `VERCEL_GIT_COMMIT_SHA`/`VERCEL_DEPLOYMENT_ID` não existem; a rota virou **`force-dynamic`** (lê `VERCEL_DEPLOYMENT_ID`, garantido em runtime) → cache versionado por deploy (unit test pina `dynamic`); (b) **subscribe não idempotente** — segundo toque em "Ativar neste dispositivo" no mesmo device devolvia a mesma subscription do browser e estourava o UNIQUE do `endpoint` (erro falso com o device já ativo); `subscribeCampaignPush` agora faz find-before-create e responde sucesso. **Ferramenta nova:** `pnpm db:check:push-chain` (`scripts/check-push-chain.mjs`, read-only — `PROD_DATABASE_URL` obrigatória, conexões com `default_transaction_read_only`) fecha um veredito por elo (SW servido/versionado, envs, consentimento, inscrições por usuário, log de eventos) com exit code. **Ops:** par VAPID gerado e adicionado em **Production** (`VAPID_PUBLIC_KEY`/`VAPID_PRIVATE_KEY`/`VAPID_SUBJECT`/`NEXT_PUBLIC_VAPID_PUBLIC_KEY`); preview ficou de fora — o projeto não tem Git repo conectado (deploys CLI) e `vercel env add ... preview` exige branch (`branch_not_found`); `NEXT_PUBLIC_*` precisa do build do próximo deploy para inliner. **Pendência humana:** teste em device real (Android Chrome) após o deploy — ativar em `/campanha/perfil`, disparar evento, conferir banner + toque; `db:check:push-chain` antes/depois. Sem migration, access ou Consent (fail-closed intacto). Intenção [`push-notificacoes-dispositivo-producao.md`](plans/push-notificacoes-dispositivo-producao.md); impl [`push-notificacoes-dispositivo-producao-impl.md`](plans/push-notificacoes-dispositivo-producao-impl.md). + **Recently resolved (2026-08-10):** **C106** (Issue #517) — feed de atualizações `/campanha/atualizacoes` sem moldura no mobile: **cards edge-to-edge** (`-mx-4` no ul) sem moldura/arredondamento com uma linha separadora (`border-b last:border-b-0` — padrão exato dos cards do B184) + **empty state com copy única** "Ajuste o filtro ou registre um novo fato de campo." (descreve a ação, vale nas duas resoluções; o texto antigo citava o botão "+ Nova atualização"). O filtro sticky/sem label/moldura e o icon "Nova atualização" no header **foram entregues pelo irmão B184/C107 em `main`** (`campaignListOmniboxFormClassName` + chassis borderless < md + `campaignMobileHeaderIconClassName`, id `campaign-updates-new`) — no rebase, a versão do irmão venceu o conflito em `CampaignUpdatesFilters.tsx` (edit the owner, não twin; a hipótese original de CSS de página no padrão C101 foi descartada na execução). **Bug pré-existente corrigido no caminho:** o `@layer base` editorial aplica `my-6 ml-6 list-disc [&>li]:mt-2` a todo `ul` — o feed era a única lista campaign em `
      ` (indent de 24px + 8px por li; precedente do patch: `[data-theme='campaign'] p { margin-top: 0 }`); a lista neutraliza na própria classe. **Achado registrado:** páginas dinâmicas (searchParams awaitado) têm um nó `#S:0` hidden **transitório** durante o streaming (cópia da página; some em ~1–5s; só no build de produção) — os e2e aguardam o stream assentar (`settleStream`). E2E novo `tests/e2e/campaignUpdatesMobile.e2e.spec.ts` (mobile: label oculta, form sticky + linha, icon abre o bottom sheet, cards edge-to-edge; empty state; regressão desktop). Sem migration/access/Consent. Intenção [`atualizacoes-mobile-sem-moldura.md`](plans/atualizacoes-mobile-sem-moldura.md); impl [`atualizacoes-mobile-sem-moldura-impl.md`](plans/atualizacoes-mobile-sem-moldura-impl.md). -1c6b3d6e (feat(C106): cards do feed edge-to-edge com linha + empty state (padrão B184/C107 compartilhado)) **Recently resolved (2026-08-09):** **B183** (Issue #501) — o auto-zoom do Safari iOS em inputs do PWA `/campanha` (font < 16px ao focar, escala que não volta no standalone) foi eliminado pela **causa**: uma regra CSS única em `styles.css` — `@media (pointer: coarse) and (hover: none)` + `[data-theme='campaign'] :is(input, select, textarea, [contenteditable='true']) { font-size: 1rem }` — cobrindo omnibox B127, Command search, native-select e textareas em qualquer orientação do iPhone (landscape chega a 932px de largura, o que descartou escopo por `max-width`). Unlayered vence as utilities `text-sm` sem `!important`; desktop (pointer fine) fica intocado; `Input`/`Textarea` do ui já eram `text-base md:text-sm`. Sem migration, access, Consent ou mudança de componente. E2E novo `tests/e2e/campaignIosInputZoom.e2e.spec.ts` pina o contrato: emulação `devices['iPhone 13']` → omnibox de `/campanha/municipios` computa **16px**; control desktop → permanece **14px**. Intenção [`pwa-ios-autozoom-input-foco.md`](plans/pwa-ios-autozoom-input-foco.md); impl [`pwa-ios-autozoom-input-foco-impl.md`](plans/pwa-ios-autozoom-input-foco-impl.md). -f37b8bcd (feat(C106): atualizações mobile sem moldura — omnibox sticky, criar no header, cards edge-to-edge) **Recently resolved (2026-08-10):** **B185** (Issue #524) — o Sollinha ganhou a tool **`getPendingLeaderships`** (`src/utilities/ai/tools/getPendingLeaderships.ts`): "quais lideranças ainda precisam ser abordadas" por **território de identidade** (grafia tolerante a acento — "Vale do Jiquiriça" resolve para a região canônica), **cidade** ("Salvador" = as 19 ZE juntas) ou **município** (aliases canônicos), com o **critério de pendência sempre declarado na resposta** (`'A abordar'/'Em disputa', ou 'Engajado' sem compromisso de votos no escopo consultado` — eixo do compromisso = presença de `votePledge` liderança×município no escopo; `negativo` é excluído na query, fail-closed), lista com nome/municípios/status/última atualização/**assessores responsáveis** (nomes resolvidos em query própria — nunca `depth` populando `campaignUser`), top N (default 20, max 50) com `total` e `truncado`, escopo omitido = escopo de acesso do usuário. Filtro **C3** `sem_assessor` (`advisors: { exists: false }`, precedente B176) e modo **C4** `municipios_sem_lideranca` (municípios do escopo sem nenhuma liderança). **Gate staff inline** no topo do `execute` (leader → `{ error: 'Leitura de dados de lideranças negada.' }` antes de qualquer query, mesmo shape da B180; 1 call site — sem helper); leituras todas `overrideAccess: false, user: ctx.user` (RBAC atual). Links via `buildCampaignLinks` existente (nada mudou lá); seção nova no system prompt manda declarar o critério. Unit tests novos (`pendingLeadershipsTool.unit.spec.ts`, 14) — gate fail-closed com payload intocado, critério por status+pledge, pledge fora do escopo não satisfaz (where `municipality in`), escopos com acento/Salvador/município/erro, `sem_assessor` no where, C4, truncamento, nomes de assessores; allowlist na convenção P3-D justificada (filtro de domínio sobre set resolvido ≠ escopo de actor). Sem migration, access change ou Consent. Intenção [`sollinha-liderancas-pendentes-abordagem.md`](plans/sollinha-liderancas-pendentes-abordagem.md); impl [`sollinha-liderancas-pendentes-abordagem-impl.md`](plans/sollinha-liderancas-pendentes-abordagem-impl.md). **Recently resolved (2026-08-09):** **C104** (Issue #505) — compromissos **"Todo o dia"** na agenda: o sheet de criação rápida (C91) e o formulário completo ganharam o toggle **Todo o dia** (checkbox; no sheet entre o título e Início/Término, no form no topo do card "Data e horário") — ligado, Início e Término viram **só datas** (término pode ser outro dia = faixa de vários dias inteiros; dia único = `startAt === endAt`). Representação: campo novo `allDay` (migration `20260810_010844_add_activity_all_day`) + convenção de instantes **fim inclusivo** (startAt = 1º dia 00:00 America/Bahia, endAt = último dia 00:00; Bahia sem DST); conversões exclusivo↔inclusivo (FullCalendar/iCal usam fim exclusivo) concentradas no módulo puro `lib/activityAllDay.ts` (unit-pinado: dia único, multi-dia, virada de mês/ano, rótulo "10/08/2026 a 12/08/2026"). Calendário: `allDaySlot` ligado (faixa no topo de semana/dia), `toEventInput` com `allDay`, reschedule converte os `startStr`/`endStr` exclusivos e envia `allDay`; **guard de drop**: arrastar entre a lane all-day e a grade de horário é rejeitado (revert + toast — converter por drag é feature fora do escopo). "Mais detalhes" preserva a escolha (`allDay=1` + datas civis nos params; form nasce com toggle ligado). Rótulos all-day em card, overview, dossiê do município, cabeçalho de detalhe e home search; feed iCal exporta `DTSTART;VALUE=DATE`/`DTEND;VALUE=DATE` com fim exclusivo. Liderança não consegue flipar `allDay` (snapshot staff). Sem access/Consent. Intenção [`agenda-toggle-todo-o-dia.md`](plans/agenda-toggle-todo-o-dia.md); impl [`agenda-toggle-todo-o-dia-impl.md`](plans/agenda-toggle-todo-o-dia-impl.md). -**Recently resolved (2026-08-09):** **D6** (Issue #521) — push no device fechado de ponta a ponta. **Diagnóstico com evidência por elo** (leitura read-only de prod): (1) **envs VAPID ausentes no Vercel production** (verificado com `vercel env ls production`) — elo raiz: sem elas o servidor nunca chama o web-push e o card de perfil desabilita "Ativar"; (2) consentimento `campanha-notificacoes-push` já publicado (Onda 0, 2026-07-19); (3) 0 inscrições `pushSubscription` (bloqueado pelo elo 1); (4) 74 `Notification` geradas no sino (eventos fluem) mas **zero envios**; (5) banner no OS = passo humano (Android). **Dois defeitos de código corrigidos com evidência:** (a) o SW `/campanha/sw.js` servia `CACHE_NAME "campanha-dev"` em prod — deploys são CLI `vercel build --prebuilt` (git builds ignorados), e a rota `force-static` congelava o buildId no build onde `VERCEL_GIT_COMMIT_SHA`/`VERCEL_DEPLOYMENT_ID` não existem; a rota virou **`force-dynamic`** (lê `VERCEL_DEPLOYMENT_ID`, garantido em runtime) → cache versionado por deploy (unit test pina `dynamic`); (b) **subscribe não idempotente** — segundo toque em "Ativar neste dispositivo" no mesmo device devolvia a mesma subscription do browser e estourava o UNIQUE do `endpoint` (erro falso com o device já ativo); `subscribeCampaignPush` agora faz find-before-create e responde sucesso. **Ferramenta nova:** `pnpm db:check:push-chain` (`scripts/check-push-chain.mjs`, read-only — `PROD_DATABASE_URL` obrigatória, conexões com `default_transaction_read_only`) fecha um veredito por elo (SW servido/versionado, envs, consentimento, inscrições por usuário, log de eventos) com exit code. **Ops:** par VAPID gerado e adicionado em **Production** (`VAPID_PUBLIC_KEY`/`VAPID_PRIVATE_KEY`/`VAPID_SUBJECT`/`NEXT_PUBLIC_VAPID_PUBLIC_KEY`); preview ficou de fora — o projeto não tem Git repo conectado (deploys CLI) e `vercel env add ... preview` exige branch (`branch_not_found`); `NEXT_PUBLIC_*` precisa do build do próximo deploy para inliner. \*\*Pe... (line truncated to 2000 chars) +**Recently resolved (2026-08-09):** **C102** (Issue #498) — no mobile (<768px), nenhuma tela `/campanha` de staff mostra mais hamburger nem sheet de sidebar: a navegação staff mora na barra inferior + drawer "Mais". `CampaignMobileTopBar` esconde o `SidebarTrigger` via `useCampaignPageChromeRole` + `isStaffCampaignRole` (leader mantém — lockdown sem barra inferior); `CampaignSidebar` retorna `null` quando `isMobile && staff`, desmontando o Sheet; **B18 (filtros salvos de Municípios) migrou para o drawer "Mais"** — `MunicipalityNavSavedFilters` ganhou `variant: 'overflow'` (mesma store `useMunicipalitySavedFilters`, mesma lógica apagar/desfazer/sucessão de foco; no drawer o fallback de foco vira o link Perfil do footer) e `CampaignBottomNav` passou a renderizá-la entre destinos e footer (container com `overflow-y-auto`). **Bug pré-existente corrigido no caminho (D4):** o `` vivia dentro do wrapper `[contain:layout_paint]` do painel da AI — `contain: paint` vira containing block para `position: fixed` e cria stacking context próprio, prendendo o toast (z 999999999 do sonner) ABAIXO dos portais z-50 de drawer/sheet: qualquer toast com ação (ex. "Desfazer") era inalcançável com modal aberto; o Toaster foi movido para o root layout `(campaign)/layout.tsx` (irmão do `{children}`, direto no ``). Testes: 2 unit novos na top bar (staff sem trigger / leader com trigger), 3 e2e novos (staff mobile sem trigger e sem dialog Sidebar; leader abre o sheet com "Meus contatos"; fluxo completo salvar→navegar→apagar→desfazer no drawer). Sem migration, access ou Consent. Intenção [`c102-mobile-sem-botao-sidebar.md`](plans/c102-mobile-sem-botao-sidebar.md); impl [`c102-mobile-sem-botao-sidebar-impl.md`](plans/c102-mobile-sem-botao-sidebar-impl.md). **Recently resolved (2026-08-09):** **B179** (Issue #462) — o Sollinha passou a falar de si **no masculino**: o system prompt (`src/utilities/ai/systemPrompt.ts`) abre com "Você é **o** Sollinha" e ganhou regra explícita (auto-referência masculina em primeira pessoa, nunca flexões femininas sobre si) — o welcome da UI já era masculino; o placeholder do chat virou "Pergunte para o Sollinha..." (`CampaignAIChat.tsx`) com os seletores e2e acompanhando (2 specs). Sem migration, access, Consent ou guarda automatizada (decisão do gate: só édito de texto). Os planos entregues da família (B162/B166/B167/B173/B174/B177) permanecem frozen com "a Sollinha" histórico — o que vale é a voz no runtime e o que se escreve daqui pra frente. Intenção [`sollinha-genero-masculino.md`](plans/sollinha-genero-masculino.md); impl [`sollinha-genero-masculino-impl.md`](plans/sollinha-genero-masculino-impl.md). diff --git a/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md b/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md new file mode 100644 index 00000000..7c762dfd --- /dev/null +++ b/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md @@ -0,0 +1,83 @@ +# Impl: Restaurar entradas B183/C102 perdidas do docs/CHANGELOG-AGENTS.md + +Status: aprovado (gate humano 2026-08-10) +Atualizado em: 2026-08-10 +Issue: #557 +Intenção: docs/plans/restaurar-entradas-changelog-b183-c102.md +Appetite restante: ~0,25 dia eng (herdado; exploração já consumida, execução é minutos) + +## Leitura da intenção + +- **Outcome:** as entradas de entrega perdidas do CHANGELOG voltam ao main, sem duplicar nem alterar texto (fonte: git), na posição cronológica correta; nada mais é tocado; sem Issue nova por entrada. +- **O que NÃO negociar:** texto canônico vindo do git (`8c79eddf`); posição relativa de C102 (após C104, antes de B179 — mesma ordem de `8c79eddf`); nenhuma outra **entrada legítima** é editada/removida; sem guarda automatizada de presença. +- **O que reavaliar:** a hipótese da intenção ("só B183 e C102 sumiram") está **desatualizada** — o snapshot da intenção (2026-08-10, pré-merges do B184/C103/C106/B185) não viu o churn do rebase do C106 (PR #555), que (a) **perdeu também a entrada da C108** (escrita em `c3f10cce`, ausente em main) e (b) deixou lixo de resolução de conflito no topo: D6 duplicado, C106 duplicado e 2 linhas de `git log` vazadas no markdown. A B183, que a intenção manda restaurar, **já foi re-adicionada** pelo rebase da B184 (`fab834fc`), byte-idêntica à fonte — restaurá-la de novo criaria duplicata. + +## Abordagem recomendada + +```mermaid +flowchart LR + A[Inventário verificado no git] --> B[C102 restaurada de 8c79eddf no lugar do D6 duplicado] + A --> C[C108 restaurada de c3f10cce no topo da seção] + A --> D[D6 duplicado removido
      C106 v1 removida
      2 linhas git vazadas removidas] + A --> E[B183: verificar presente e idêntica - sem ação] + D --> F[git diff: só as mudanças esperadas] + F --> G[grep de sanidade + format:check + gate:fast] + G --> H[commit + pnpm push + PR Closes #557] +``` + +**Opções consideradas:** A | B | C +**Recomendação:** A — restauração cirúrgica das 2 entradas perdidas (C102 + C108) + remoção do lixo do mesmo churn (dup D6 no lugar da C102, dup C106 v1, 2 linhas vazadas). É a única opção que deixa a seção íntegra: uma entrada por entrega, sem texto alterado, sem conteúdo canônico perdido. +**Rejeitadas:** + +- B — executar a intenção ao pé da letra (restaurar só C102): deixa C108 perdida, D6 duplicado, C106 duplicado e linhas de commit vazadas como defeito permanente num doc que agentes leem para contexto. A intenção não previu o churn posterior; "nenhuma outra entrada é tocada" refere-se a entradas legítimas, não a lixo de conflito. +- C — reordenar a seção topo inteira pela ordem de merges: viola o "não reorganizar o arquivo" da intenção e mexe em entradas legítimas (B184/C103/B185…). O custo (diff grande, risco de desordenar o que está aceitável) não compra nada para o aceite. +- D — registrar Issues novas para as entregas que **nunca** escreveram entrada (B187/B188/B189/B190/B191/C107 — verificado: branch tips e merges não tocaram o CHANGELOG): proibido pela intenção ("Sem Issue nova por entrada"); é gap de processo, não perda. Registrado como nota no impl e levado ao capture-review-debts se o humano quiser. + +### Componentes / mudanças + +- **`docs/CHANGELOG-AGENTS.md`** (único arquivo): edição de ~5 linhas no topo da seção "Recently resolved". + 1. **C102** — linha inteira de `8c79eddf` (linha 11), colada verbatim, no lugar do D6 duplicado (atual linha 23, entre C104 e B179). `git diff` contra o arquivo da intenção confirma texto idêntico. + 2. **C108** — linha inteira de `c3f10cce` (linha 7), colada verbatim, no **topo da seção** (acima da B184): era a posição dela quando escrita (mais nova na época) e é a posição menos ambígua para uma entrada restaurada; datada 2026-08-10 como a B184. + 3. **D6 duplicado** (linha 11 vs 23) — o da linha 23 é substituído pela C102; o da linha 11 (posição canônica) permanece → D6 único. + 4. **C106 duplicado** (linhas 12–13) — remove a v1 (2026-08-09, rascunho pré-rebase; a própria v2 a descreve como superada: "a hipótese original de CSS de página … foi descartada na execução"); mantém a v2 (2026-08-10, texto final reconciliado, com E2E). + 5. **Linhas vazadas** — `1c6b3d6e (…)` e `f37b8bcd (…)` (saídas cruas de `git log --oneline` coladas no markdown) — removidas. + 6. **B183** — verificação apenas: presente na linha 16 e byte-idêntica à fonte (já demonstrado: linha de contexto no diff `8c79eddf→main`). Nenhuma ação. +- **Migration:** sem migration. +- **Access / Consent:** não se aplica. +- **UI:** não se aplica (docs-only; CI `build-affected` pula o build). + +### Dados → forma (se aplicável) + +Não se aplica (sem dados/forma). + +## Fases verificáveis + +1. **Fonte + colagem** — extrair as 2 linhas canônicas com `git show` (C102 de `8c79eddf:…`; C108 de `c3f10cce:…`), colar verbatim (nunca redigitar — evita drift de transcrição). +2. **Edição cirúrgica** — aplicar os 5 pontos acima no arquivo; `git diff` deve mostrar: +2 linhas de entrada (C102, C108), −1 entrada (C106 v1), −2 linhas vazadas, −1 D6 duplicado (substituído pela C102). Nenhuma outra linha muda. +3. **Sanidade** — `grep -c "C102\|C108"` = 2 em posições esperadas; `grep -c "D6"` = 1; `grep -c "C106"` = 1; zero linhas casando `^[0-9a-f]{7,40} (`; `git show 8c79eddf` diffs das 2 entradas restauradas = vazio; `pnpm format:check`; `pnpm gate:fast`. +4. **Entrega** — commit com o impl plan incluso; `pnpm push -u origin HEAD`; PR Ready `--base main` + `Closes #557` + auto-merge; `gh pr checks --watch --required`. + +## Rabbit holes / Não escopo (engenharia) + +- Não criar guarda automatizada de presença de entradas no CHANGELOG (intenção; processo = revisar diff em merges que o toquem). +- Não reordenar a seção nem reorganizar o arquivo. +- Não restaurar C107/B187/B188/B189/B190/B191/C105 (nunca escritas — gap de processo; fora do aceite). +- Não criar Issue por entrada nem editar outras Issues in-progress. + +## Riscos e mitigação + +- **Drift de transcrição na colagem** → colagem via `git show | sed -n` com verificação de diff (fase 1 e 3); nunca redigitar. +- **Conflito com merge em andamento** (B186/B189 locais não mergeados também contêm c3f10cce) → `pnpm push` + rebase na branch antes do PR se necessário; o fix é no arquivo, conflito resolve-se a favor da versão restaurada. +- **gate:fast lento** (docs-only muda nada) → rodar mesmo assim (exigência da skill); esperado green em lint/typecheck/unit. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (C102/C108 restauradas; B183 já presente e idêntica; texto canônico intacto) +- [x] Invariantes AGENTS/engineering-standards (docs-only; sem migration/access/Consent/DB) +- [x] Testes de domínio previstos: nenhum (docs); gate:fast + grep de sanidade + diff verificado + +--- + +### Nota de achado (gap de processo, fora do escopo) + +Entregas mergeadas sem entrada no CHANGELOG (verificado em branch tips e merges): B187 (#559), B188 (#548), B189, B190 (#560), B191 (#554), C107 (#552). A B186/B189 não mergeadas seguem em branch. Recomendação de processo (não automatizada): revisar o diff do CHANGELOG no rebase de cada branch que o toque — o mesmo processo que a intenção já registra. From 1afe2aeed622f3fe0a72ce33410cbd3e0e842ac8 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Mon, 10 Aug 2026 08:02:45 -0300 Subject: [PATCH 2/2] =?UTF-8?q?fix(D8):=20restaura=20entrada=20D6=20trunca?= =?UTF-8?q?da=20pelo=20rebase=20do=20C104=20(revis=C3=A3o=20do=20/simplify?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D6 (Issue #521) voltou verbatim de a4d7b883 (merge do D6; idêntica a 8c79eddf): o rebase do C104 (ea700e31) tinha cortado o final 'Pendência humana: …' e o prettier (d52d0016) escapou '**Pe' -> '\*\*Pe' literal no markdown - Impl plan: corrige atribuição da re-adição da B183 (7eaa65ce/PR #555, não fab834fc) e classificação do B189 (não mergeada; entrada no tip da branch) --- docs/CHANGELOG-AGENTS.md | 1 + docs/plans/restaurar-entradas-changelog-b183-c102-impl.md | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index e74aef88..8ebd0e47 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -3,6 +3,7 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agentes paralelos): "Known Gaps" e todos os blocos "Recently resolved". Histórico — leia só quando precisar do contexto de uma entrega passada; o sempre-presente fica no `AGENTS.md`. Itens novos de "Recently resolved" passam a ser registrados aqui (uma entrada curta), não no AGENTS.md. --- + **Recently resolved (2026-08-10):** **OPS21** (Issue #538) — `REVALIDATE_SECRET` adicionada na **Vercel production** (projeto `solla/jorgesolla`): diagnóstico D6 confirmado (env ausente em `vercel env ls production`; `POST /api/revalidate` respondia `500` "not configured" em prod, quebrando o runbook pós-seed/mudança direta de DB). Segredo gerado com `openssl rand -hex 32` (valor via stdin, nunca argv), `vercel env add REVALIDATE_SECRET production`; **fallback necessário:** o deploy existente congelava as envs em runtime → `vercel redeploy` do último deploy de produção (2×, pois o primeiro `env add` via pipe herdou o `\n` do `openssl` e gravou valor de 66 chars — removido e re-adicionado limpo). Validação real em prod: sem secret → `401`; secret errado → `401`; secret certo → `200 { revalidated: true, tag: 'posts' }`; tag desconhecida → `400`. Preview não adicionada (projeto sem Git conectado — `vercel env add ... preview` exige branch, precedente D6). Sem código/migration/access/UI. Impl [`revalidate-secret-vercel-production-impl.md`](plans/revalidate-secret-vercel-production-impl.md). **Recently resolved (2026-08-10):** **C108** (Issue #522) — sino de notificações: **abrir já lê** — `openCampaignNotifications` (action única) lista + marca tudo como lido em 1 round trip, substituindo `listCampaignNotifications` + `markAllCampaignNotificationsRead`; o badge zera na abertura sem clique e o botão "Marcar todas como lidas" deixa de existir. Painel limpo em qualquer tamanho: sem título "Notificações" visível (sr-only para a11y), sem contador, sem botão "Fechar"; desktop vira **modal central** (Dialog shadcn, X no canto superior direito, clique fora fecha — par responsivo Dialog/Drawer do C94 via `useIsMobile`); mobile segue bottom sheet com handle, safe-area e **skeleton de loading** (o painel não abre mais em branco durante o round trip). `markAllNotificationsRead` extraída para `src/utilities/notification/notificationList.ts` (owner-scoped com bypass documentado, idempotente, cap 200 mantido; int testado: marca só as próprias, deixa as do outro intactas); `readAt` saiu do contrato `NotificationListItem` (sem leitores após a remoção do destaque de não lida); copy de erro unificada em `CAMPAIGN_NOTIFICATION_LOAD_ERROR_MESSAGE`; `router.refresh()` re-sincroniza a instância irmã do outro header só quando algo foi marcado (race cross-tab documentado); foco visível nos itens e `role="alert"` no erro. P4-D atualizado no ledger — o batch sem transação agora dispara a cada abertura (self-healing). Testes: int (owner-scope + idempotência) e unit novos do bell (dialog limpo, refresh condicional, empty/error, sheet mobile). Sem migration, access, Consent ou schema. Intenção [`sino-notificacoes-leitura-auto-modal-desktop.md`](plans/sino-notificacoes-leitura-auto-modal-desktop.md); impl [`sino-notificacoes-leitura-auto-modal-desktop-impl.md`](plans/sino-notificacoes-leitura-auto-modal-desktop-impl.md). diff --git a/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md b/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md index 7c762dfd..69adf436 100644 --- a/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md +++ b/docs/plans/restaurar-entradas-changelog-b183-c102-impl.md @@ -10,7 +10,7 @@ Appetite restante: ~0,25 dia eng (herdado; exploração já consumida, execuçã - **Outcome:** as entradas de entrega perdidas do CHANGELOG voltam ao main, sem duplicar nem alterar texto (fonte: git), na posição cronológica correta; nada mais é tocado; sem Issue nova por entrada. - **O que NÃO negociar:** texto canônico vindo do git (`8c79eddf`); posição relativa de C102 (após C104, antes de B179 — mesma ordem de `8c79eddf`); nenhuma outra **entrada legítima** é editada/removida; sem guarda automatizada de presença. -- **O que reavaliar:** a hipótese da intenção ("só B183 e C102 sumiram") está **desatualizada** — o snapshot da intenção (2026-08-10, pré-merges do B184/C103/C106/B185) não viu o churn do rebase do C106 (PR #555), que (a) **perdeu também a entrada da C108** (escrita em `c3f10cce`, ausente em main) e (b) deixou lixo de resolução de conflito no topo: D6 duplicado, C106 duplicado e 2 linhas de `git log` vazadas no markdown. A B183, que a intenção manda restaurar, **já foi re-adicionada** pelo rebase da B184 (`fab834fc`), byte-idêntica à fonte — restaurá-la de novo criaria duplicata. +- **O que reavaliar:** a hipótese da intenção ("só B183 e C102 sumiram") está **desatualizada** — o snapshot da intenção (2026-08-10, pré-merges do B184/C103/C106/B185) não viu o churn do rebase do C106 (PR #555), que (a) **perdeu também a entrada da C108** (escrita em `c3f10cce`, ausente em main) e (b) deixou lixo de resolução de conflito no topo: D6 duplicado, C106 duplicado e 2 linhas de `git log` vazadas no markdown. A B183, que a intenção manda restaurar, **já foi re-adicionada** pela branch da C106 (`7eaa65ce`, PR #555 — `fab834fc`/B184 não a tocou), byte-idêntica à fonte — restaurá-la de novo criaria duplicata. E o rebase do C104 (`ea700e31`) **truncou a entrada da D6** (perdeu o final "Pendência humana: …"; o prettier `d52d0016` escapou `**Pe` → `\*\*Pe`) — achado dos revisores do /simplify, corrigido na mesma sessão (fase 2). ## Abordagem recomendada @@ -31,7 +31,7 @@ flowchart LR - B — executar a intenção ao pé da letra (restaurar só C102): deixa C108 perdida, D6 duplicado, C106 duplicado e linhas de commit vazadas como defeito permanente num doc que agentes leem para contexto. A intenção não previu o churn posterior; "nenhuma outra entrada é tocada" refere-se a entradas legítimas, não a lixo de conflito. - C — reordenar a seção topo inteira pela ordem de merges: viola o "não reorganizar o arquivo" da intenção e mexe em entradas legítimas (B184/C103/B185…). O custo (diff grande, risco de desordenar o que está aceitável) não compra nada para o aceite. -- D — registrar Issues novas para as entregas que **nunca** escreveram entrada (B187/B188/B189/B190/B191/C107 — verificado: branch tips e merges não tocaram o CHANGELOG): proibido pela intenção ("Sem Issue nova por entrada"); é gap de processo, não perda. Registrado como nota no impl e levado ao capture-review-debts se o humano quiser. +- D — registrar Issues novas para as entregas mergeadas que **nunca** escreveram entrada (B187/B188/B190/B191/C107/C105 — verificado: branch tips e merges não tocaram o CHANGELOG): proibido pela intenção ("Sem Issue nova por entrada"); é gap de processo, não perda. Registrado como nota no impl e levado ao capture-review-debts se o humano quiser. ### Componentes / mudanças @@ -61,7 +61,7 @@ Não se aplica (sem dados/forma). - Não criar guarda automatizada de presença de entradas no CHANGELOG (intenção; processo = revisar diff em merges que o toquem). - Não reordenar a seção nem reorganizar o arquivo. -- Não restaurar C107/B187/B188/B189/B190/B191/C105 (nunca escritas — gap de processo; fora do aceite). +- Não restaurar C107/B187/B188/B190/B191/C105 (nunca escritas — gap de processo; fora do aceite). B186/B189 não mergeadas — entradas vêm com os merges. - Não criar Issue por entrada nem editar outras Issues in-progress. ## Riscos e mitigação @@ -80,4 +80,4 @@ Não se aplica (sem dados/forma). ### Nota de achado (gap de processo, fora do escopo) -Entregas mergeadas sem entrada no CHANGELOG (verificado em branch tips e merges): B187 (#559), B188 (#548), B189, B190 (#560), B191 (#554), C107 (#552). A B186/B189 não mergeadas seguem em branch. Recomendação de processo (não automatizada): revisar o diff do CHANGELOG no rebase de cada branch que o toque — o mesmo processo que a intenção já registra. +Entregas mergeadas sem entrada no CHANGELOG (verificado em branch tips e merges): B187 (#559), B188 (#548), B190 (#560), B191 (#554), C107 (#552), C105 (#536). B186 e B189 seguem **não mergeadas** em branch (a B189 escreveu entrada no tip da branch em 2026-08-10 — virá junto com o merge; a B186 ainda não tem). Recomendação de processo (não automatizada): revisar o diff do CHANGELOG no rebase de cada branch que o toque — o mesmo processo que a intenção já registra.