diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index c439478d..b116a07e 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,7 +4,7 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- -**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-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). diff --git a/docs/plans/municipios-mobile-sem-moldura-impl.md b/docs/plans/municipios-mobile-sem-moldura-impl.md new file mode 100644 index 00000000..41f139ff --- /dev/null +++ b/docs/plans/municipios-mobile-sem-moldura-impl.md @@ -0,0 +1,92 @@ +# Impl: B184 — Omnibox mobile sem moldura (padrão de todas as listas) + cards edge-to-edge e salvar no header (municípios) + +Status: entregue em código (2026-08-09) +Atualizado em: 2026-08-10 +Issue: #514 +Intenção: docs/plans/municipios-mobile-sem-moldura.md +Appetite restante: ~0,5–1 dia eng (herdado; abordagem dentro do envelope) + +## Leitura da intenção + +- **Outcome:** no celular, o chrome do filtro de listas some: label oculta, omnibox sem moldura e sticky sob a barra superior com linha separadora, limpar vira X circular dentro do input (com chips **ou** busca digitada). **Escopo ampliado por decisão do humano (2026-08-09):** o look bare/sticky é o **padrão mobile do chassis `CampaignListOmnibox` — todas as listas `/campanha`**, não só municípios. Em municípios, adicionalmente: "Salvar filtro" vira icon button no header (mobile) e os cards ficam sem borda, edge-to-edge, separados por linha. +- **O que NÃO negociar:** desktop de todas as listas inalterado (label visível, box com borda, botão texto "Limpar", sem X); comportamento de filtragem (URL/navegação) intacto em todas as listas; chips continuam dentro da omnibox; fluxo do popover de salvar (nomear/renomear) intacto; cards edge-to-edge e salvar no header são municípios-only (outras listas não têm essa superfície/saved filters); sem dados/KPI novos. +- **O que reavaliar:** a hipótese da intenção escopava a municípios; a decisão do humano tornou o chassis o dono do padrão — prop opt-in morre, o look bare vira o default < `md` do chassis, e os 11 call sites mudam juntos. Cards e header continuam nos arquivos de municípios. + +## Abordagem recomendada + +```mermaid +flowchart LR + subgraph Chassis["CampaignListOmnibox (padrão < md, todos os 11 call sites)"] + L[Label: hidden md:block
input ganha aria-label={label} sempre] + B[Box: border-0 shadow-none md:border md:border-input md:shadow-xs] + S[Coluna do input: sticky top-0 z-20 -mx-4 border-b bg-background px-4 py-2
md:static md:mx-0 md:border-b-0 md:bg-transparent] + X["X circular no input: onClearAll && (chips || query)
aria-label='Limpar', md:hidden"] + C["'Limpar' texto: hidden md:inline-flex"] + end + subgraph Municipio["Municípios (adicional)"] + M2[SaveMunicipalityFilterControl no trailing: hidden md:inline-flex] + H1["page.tsx → SetCampaignHeaderAction → SaveMunicipalityFilterControl presentation='icon' + md:hidden"] + K1["Cards: gap-0 -mx-4 md:gap-4 md:mx-0; article border-b last:border-b-0 md:border md:rounded-xl"] + end + Chassis --> Municipio +``` + +**Opções consideradas:** + +- **A — Look bare como padrão do chassis (`CampaignListOmnibox`), sem prop.** Todos os 11 call sites herdam; desktop via variantes `md:`; nada muda nos adapters/URLs. +- **B — Prop opt-in `mobileBare` (plano anterior), só municípios.** +- **C — Cópia local do chassis para municípios.** + +**Recomendação:** A — a decisão do humano fez do look o padrão; prop opt-in viraria fricção para toda lista futura. O chassis é o dono do concern (B127) e não há divergência entre listas: todas passam `onClearAll`, todas limpam busca + filtros no `kind: 'clear'` (verificado em todos os 11 runActions), nenhuma tem trailing que precise ficar na região sticky (picker e ações ficam abaixo, fora da sticky, e as ações desktop-only já são `hidden md:flex`). Desktop continua 100% no comportamento atual via variantes `md:`. + +**Rejeitadas:** B porque o pedido agora é o padrão — cada lista futura teria que lembrar da prop; C porque duplica o contrato ARIA/combobox do chassis (o próprio plano da intenção proíbe "twin" do dono existente). + +### Componentes / mudanças + +- **`CampaignListOmnibox`** (`src/components/campaign/shared/CampaignListOmnibox.tsx`) — sem prop nova; abaixo de `md`: + - `label` → `hidden md:block` (o `