diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc
index 555cad8d..262a421f 100644
--- a/.cursor/rules/projects/nucleos-eleitorais.mdc
+++ b/.cursor/rules/projects/nucleos-eleitorais.mdc
@@ -7,7 +7,7 @@ alwaysApply: false
## Status
-- **B52 entregue em código (2026-07-29):** quarto grupo da busca global do Início — **Dobradinhas** via `searchHomeStateDeputies` + `HomeSearchStateDeputyGroup` (após Assessores); `POST /campanha/home-search` agrega `stateDeputies`; match word-start em nome ou partido; secundário `partido · N municípios`; assessor vê hits (staff read); `municipalityIdsByStateDeputyIds` em `stateDeputyData`. Int `homeSearchStateDeputies.int.spec.ts`. WA = **B55** (sem telefone na entidade). Plano: [`busca-global-resultados-dobradinhas.md`](../../../docs/plans/busca-global-resultados-dobradinhas.md). Próximo na cadeia busca: **B51** atividades.
+- **B52 entregue em código (2026-07-29):** quarto grupo da busca global do Início — **Dobradinhas** via `searchHomeStateDeputies` + `HomeSearchStateDeputyGroup` (após Atividades); `POST /campanha/home-search` agrega `stateDeputies`; match word-start em nome ou partido; secundário `partido · N municípios`; assessor vê hits (staff read); `municipalityIdsByStateDeputyIds` em `stateDeputyData`. Int `homeSearchStateDeputies.int.spec.ts`. WA = **B55** (sem telefone na entidade). Plano: [`busca-global-resultados-dobradinhas.md`](../../../docs/plans/busca-global-resultados-dobradinhas.md). Próximo na cadeia busca: **B53** demandas.
- **B72 entregue em código (2026-07-29):** gap horizontal da strip de ações do Início (UX-1) — `CampaignHomeActionStrip` `
` `gap-6` → `gap-4` (16px); `w-[4.75rem]`, círculo 48px, `snap-x`, scrollbar oculta e pan B67 ✓ inalterados. Sem migration/action/URL. Unit strip 11/11 em `campaignHomeActionButton.unit.spec.tsx`. Plano: [`espacamento-strip-acoes-inicio.md`](../../../docs/plans/espacamento-strip-acoes-inicio.md).
- **B65 entregue em código (2026-07-29):** âncora inferior mobile no Início — cadeia `h-full min-h-0` em `CampaignPageShell` + `CampaignHomeStaffChrome` + `CampaignHomeLayout`; wrapper `data-slot="home-dock"` agrupa strip B45 + busca B47; spacer `flex-1` empurra o dock acima da bottom nav (`pb-24` intacto); `md+` inalterado; modo focado B47/B66 intacto; sem `fixed`/`mt-auto`. Unit `campaignHomeLayout.unit.spec.tsx` (6 casos). Plano: [`ancorar-busca-acoes-inicio-mobile.md`](../../../docs/plans/ancorar-busca-acoes-inicio-mobile.md). Desbloqueia animação idle de **B66**. Débito adiado: ajuste fino `pb-24` vs altura real da bottom nav se gap >~8 px em device real.
- **B59 entregue em código (2026-07-29):** chassis UX-1 dos wizards de ação — `CampaignWizardShell` (`shared/`: Voltar só mobile com rótulo visível, caption sticky central de município, `contentAlign`, foco no `h1` ao trocar passo); rotas staff `/campanha/acoes/` (cinco entradas espelhando o catálogo B45, passo ponte "Em qual município?" até **B60**); `campaignActionRoutes.ts` + hrefs em `toHomeActionButtonProps`; layout `acoes/` com gate `staff`. Sem migration/action/Consent. Unit `campaignActionRoutes`, `campaignWizardShell`, `campaignHomeActions` atualizado; e2e Início → `atualizar-votos`. Plano: [`chassis-wizard-campanha.md`](../../../docs/plans/chassis-wizard-campanha.md). Desbloqueia **B60** / **B61** / **B63** / **B64**.
@@ -15,8 +15,9 @@ alwaysApply: false
- **B67 entregue em código (2026-07-29):** pan na strip de ações do Início (UX-1) — `CampaignHomeActionStrip` com drag-to-scroll em `pointer-fine` (`HOME_ACTION_STRIP_DRAG_THRESHOLD_PX` 8, `cursor-grab`/`grabbing`); scrollbar oculta (B58) mantida; touch `pan-x` nativo; `onClickCapture` suprime link após arraste. Unit em `campaignHomeActionButton.unit.spec.tsx`. Sem migration. Plano: [`restaurar-pan-strip-acoes-inicio.md`](../../../docs/plans/restaurar-pan-strip-acoes-inicio.md). Fecha Adiado drag desktop de B44.
- **B58 entregue em código (2026-07-29):** polimento visual da strip de ações do Início (UX-1) — rótulos curtos no catálogo (`Ajustar votos`, `Registrar sinal`, `Ver esquecidos`, …); removido o `h2` "O que você quer fazer?" (`aria-label` da strip = "Ações rápidas"); scrollbar horizontal oculta (pan nativo touch; drag fine em **B67** ✓); escala ~1,05 no círculo (`pointer-fine` hover / `pointer-coarse` `data-pressing`) + `active:opacity-90`; e2e B47 retarget para `[data-slot="home-actions"]`. Sem migration/action/URL. Plano: [`polimento-strip-acoes-inicio.md`](../../../docs/plans/polimento-strip-acoes-inicio.md).
- **Fill-in Cenário junto aos filtros entregue em código (2026-07-29):** em `/campanha/municipios` (staff), o seletor **Cenário** (`VoteEstimateScenarioField`) saiu do header do `MunicipalityListOverview` e entrou na barra slim de `MunicipalityFilters` (desktop: antes de Salvar/Limpar; mobile: após filtros staff, antes de Ordenar). `MunicipalityEstimateScenarioProvider` passou a envolver filtros + resultados; cenário continua estado local (fora da URL e do **Limpar**). Hint `VOTE_ESTIMATE_SCENARIO_FILTERS_HINT`. Sem migration/action/Consent. Plano: [`cenario-junto-filtros-municipios.md`](../../../docs/plans/cenario-junto-filtros-municipios.md).
-- **B49 entregue em código (2026-07-29):** segundo grupo da busca global do Início — **Lideranças** via `searchHomeLeaderships` + `HomeSearchLeadershipGroup` (entre Municípios e Assessores); payload `leaderships` no mesmo `POST /campanha/home-search`; empty state considera todos os grupos (`homeSearchHasAnyHits`); linha → `/campanha/liderancas/[id]`; `HomeSearchHitRow` compartilhado. Plano: [`busca-global-resultados-liderancas.md`](../../../docs/plans/busca-global-resultados-liderancas.md). Próximo: **B51** atividades.
-- **B50 entregue em código (2026-07-29):** grupo **Assessores** na busca global do Início — `searchHomeAdvisors` (só CG/candidato; assessor recebe `[]`); match word-start no nome; secundário = N municípios; `HomeSearchAdvisorGroup` após Lideranças; `municipalityIdsByAdvisorIds` exportado de `advisorData`. Int `homeSearchAdvisors.int.spec.ts`. WA = **B55**. Plano: [`busca-global-resultados-assessores.md`](../../../docs/plans/busca-global-resultados-assessores.md). ~~Próximo: **B51** atividades.~~ **B52** ✓ dobradinhas; **B51** atividades pendente.
+- **B49 entregue em código (2026-07-29):** segundo grupo da busca global do Início — **Lideranças** via `searchHomeLeaderships` + `HomeSearchLeadershipGroup` (entre Municípios e Assessores); payload `leaderships` no mesmo `POST /campanha/home-search`; empty state considera todos os grupos (`homeSearchHasAnyHits`); linha → `/campanha/liderancas/[id]`; `HomeSearchHitRow` compartilhado. Plano: [`busca-global-resultados-liderancas.md`](../../../docs/plans/busca-global-resultados-liderancas.md).
+- **B50 entregue em código (2026-07-29):** grupo **Assessores** na busca global do Início — `searchHomeAdvisors` (só CG/candidato; assessor recebe `[]`); match word-start no nome; secundário = N municípios; `HomeSearchAdvisorGroup` após Lideranças; `municipalityIdsByAdvisorIds` exportado de `advisorData`. Int `homeSearchAdvisors.int.spec.ts`. WA = **B55**. Plano: [`busca-global-resultados-assessores.md`](../../../docs/plans/busca-global-resultados-assessores.md).
+- **B51 entregue em código (2026-07-29):** grupo **Atividades** na busca global do Início — `searchHomeActivities` (staff-only; word-start no `title`; access `activity` com `overrideAccess: false`; rascunhos incluídos; cap 25; desempate `startAt` desc); `HomeSearchActivityGroup` após Assessores; secundário `"{município} · {quando}"`; int `homeSearchActivities.int.spec.ts`. WA = **B55**. Plano: [`busca-global-resultados-atividades.md`](../../../docs/plans/busca-global-resultados-atividades.md).
- **B48 entregue em código (2026-07-29):** primeiro provider da busca global do Início — grupo **Municípios** (municípios scoped + TIs no mesmo bloco) via `POST /campanha/home-search`, `searchHomeMunicipalities`, `HomeSearchMunicipalityGroup` no slot B47; readout 2022 alinhado à lista (`MunicipalityVotePositionReadout`). **B54** grid/cap. Plano: [`busca-global-resultados-municipios.md`](../../../docs/plans/busca-global-resultados-municipios.md).
- **B47 entregue em código (2026-07-29):** busca global do Início (UX-1) — input staff-only com debounce 250 ms (`HOME_SEARCH_DEBOUNCE_MS`), sem botão Pesquisar; modo focado quando query debounced trim ≥2 chars esconde strip B45 e spacer thumb-zone (`CampaignHomeLayout` `focused`). Ilha `CampaignHomeStaffChrome` + `CampaignHomeSearch` + `HomeSearchContext` / `useHomeSearch` para grupos B48+; contrato em `src/lib/campaignHomeSearchContract.ts`. Leader continua sem omnibox (`CampaignHomeLayout` seco). Escape limpa. Sem migration. Plano: [`busca-global-inicio-input.md`](../../../docs/plans/busca-global-inicio-input.md). ~~Próximo: **B48** resultados Municípios.~~
- **B46 entregue em código (2026-07-29):** thumb-zone do Início (UX-1 chassis) — `CampaignHomeLayout` com slots `data-slot="home-actions"` / `home-search` (ordem mobile ações→busca; desktop busca→ações). Âncora inferior corrigida por **B65** ✓. Plano: [`posicao-botoes-acao-inicio-thumb-zone.md`](../../../docs/plans/posicao-botoes-acao-inicio-thumb-zone.md).
diff --git a/AGENTS.md b/AGENTS.md
index 8b79eeb8..38d5668b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -86,6 +86,8 @@ Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.
**Latent bug fixed for free:** `CampaignTableHead`'s `description` seam (**B22**) broke **any** RSC caller — `campaignHoverTooltipAlign`/`campaignHoverExplanationClassName` lived in the `'use client'` module of `CampaignHoverTooltip`, so the server was calling a client reference; they moved to `src/lib/campaignHoverTooltip.ts`, and the leaderships list is the first RSC header with an explanation. **Post-merge `/simplify` follow-up (same day), all measured:** the perf pass cut real CPU — `buildMunicipalityPortfolioChips` 4.2 → **0.78 ms** cold per 25-row table and **0.043 ms** on every later render (a `WeakMap` keyed on the index array, which one RSC builds once and all rows share, so it survives re-renders where a per-row `useMemo` would not), and search 4.59 → **0.077 ms** per keystroke (normalize the query once instead of inside the matcher for each of ~660 candidates; `matchesNormalizedAtWordStart` is the new shared entry point and `matchesAtWordStart` delegates to it). Also: `sameIdSet` replaced a hand-rolled sorted-key comparison (3rd call site of a single-purpose helper), `scrollIntoView` is keyboard-only (the mouse is already where it points — it was a forced layout per option the pointer crossed), and `revalidatePath` now uses the real delta (`changed`), so removing a município that was never linked no longer busts its route. **One reviewer suggestion was rejected by measurement, and it looked obvious:** dropping `sort: 'name'` from the loader ("nothing depends on the DB ordering") **broke the suggestion list** — `searchMunicipalityPortfolio` walks the index in arrival order and stops at `limit`, so the alphabetical suggestions were alphabetical only because of that `ORDER BY`. The B34 e2e caught it; the sort is back with the reason written beside it. **The finding that did NOT get fixed, because both fixes oppose each other:** the delivery costs **+58 kB** First Load JS on `/campanha/liderancas` (246 → 304) and **+42 kB** on `/campanha/assessores` (253 → 295), measured with two full builds against a stashed clean `main`. Assessores already imported the static Bahia catalog, so its +42 kB is the **cell**, not the catalog — only ~16 kB of lideranças' delta is the catalog reaching a new route. Pulling the catalog out of the browser fattens the payload of every chip toggle; shrinking the payload to `{id,slug}` keeps the bundle. Registered as **B34+** with the recommendation (payload-minimal) and its one real caveat. No migration, collection, `Consent` or URL change. **Debt with a trigger:** `LeadershipStateDeputyRelationCell` (B31) keeps its own Popover and measuring machinery in the same table — **it**, not B37, is the extraction trigger for a relation-agnostic chip editor (B37 plugs the existing shared cell into a third relation without new machinery). **Measured, and now P1 in the ledger:** two e2e tests fail **deterministically at one worker on a clean `main` tree** (verified by stashing this whole delivery) — `campaignNearestMunicipality:156` and `campaignMunicipalities:394`. That is not the fichado 2-worker flake; do not dismiss a red `pnpm test:e2e` as flakiness without re-checking those two by name. Plano: [`docs/plans/chips-municipios-lista-liderancas.md`](docs/plans/chips-municipios-lista-liderancas.md).
**Recently resolved (2026-07-27):** **`users.roles`** — Payload admin RBAC. Field `roles` (`admin` | `editor`) on `users` (migration `20260727_032523_add_users_roles`, backfill `admin`); `isPayloadAdmin` requires the `admin` role; editors open `/admin` and write `post`/`tag`/`media`; PII/`petition`/campaign collections stay admin-only. Unifying `users` + `campaignUser` was rejected (25 FKs; structural isolation). Plano: [`docs/plans/roles-admin-payload.md`](docs/plans/roles-admin-payload.md).
+**Recently resolved (2026-07-29):** **B51** Busca global — grupo **Atividades** no Início staff: `searchHomeActivities` (staff-only; word-start no `title`; access `activity` com `overrideAccess: false`; rascunhos incluídos; cap 25; desempate `startAt` desc); campo `activities` no `POST /campanha/home-search`; `HomeSearchActivityGroup` após Assessores; secundário `"{município} · {quando}"`; empty via `homeSearchHasAnyHits`. Int `homeSearchActivities.int.spec.ts`. WA = **B55**. Plano: [`docs/plans/busca-global-resultados-atividades.md`](docs/plans/busca-global-resultados-atividades.md).
+
**Recently resolved (2026-07-29):** **B50** Busca global — grupo **Assessores** no Início staff: `searchHomeAdvisors` (`isUnrestrictedCampaignRole` — assessor recebe `[]`; word-start no nome; `municipalityIdsByAdvisorIds` exportado de `advisorData`); campo `advisors` no `POST /campanha/home-search`; `HomeSearchAdvisorGroup` + `HomeSearchHitRow` compartilhado; empty via `homeSearchHasAnyHits`; secundário = contagem de municípios (sem e-mail). Int `homeSearchAdvisors.int.spec.ts`. WA = **B55**. Plano: [`docs/plans/busca-global-resultados-assessores.md`](docs/plans/busca-global-resultados-assessores.md).
**Recently resolved (2026-07-28):** **B17** Seletor de colunas — a "Colunas" picker shipped as a capability of `CampaignTable`, so it landed on **all seven** list surfaces (municípios, lideranças, dobradinhas, organizações, demandas, apoiadores, territórios) rather than only on `/campanha/municipios`: pass `columnVisibility` (one value, from `readCampaignColumnVisibility(listId)`) and the table mounts `CampaignColumnPicker` and filters through `resolveVisibleColumns`. `/campanha/assessores` stays out — `AdvisorsTable` is still a raw `ui/Table` with `table-fixed`, and migrating it to the list system is separate work. **Two premises of the 2026-07-24 plan died, and the first one died on a type, not a preference:** `CampaignTableColumn.cell` is a **function**, so the column array never crosses the server/client boundary and the client cannot rebuild the table without it — hiding client-side would mean tagging every cell with `data-column` and injecting a `