` (sem `tabIndex`, sem `role`); caso contrário o render é byte a byte o de hoje. Documentar no JSDoc do tipo a regra de acessibilidade (conteúdo redundante) ao lado dos seams do B17.
+- **`CampaignHoverTooltip`** (`src/components/campaign/shared/…`, promovido no B22 ou aqui): duas props novas, opcionais e sem efeito nos call sites atuais — `openOnTouch?: boolean` (default `true`) que desliga o toggle por `pointerType === 'touch'` e o listener de `pointerdown`, e `disabled?: boolean` que força `open = false` e ignora reaberturas enquanto verdadeiro (usado por quem também abre um Popover). Hover e foco ficam intactos; o comentário de cabeçalho — que documenta o controle manual de `open`, herdado do critique do E18 — ganha o caso "gatilho que também abre Popover".
+- **`MunicipalityList.tsx`** (~290–320): a coluna `advisors` passa `cellTooltip: (municipality) => formatAdvisorNamesTooltip(advisorEntries(municipality, advisorNamesById))`, que retorna `null` quando não há assessor (a célula já diz "Sem responsável"/"Sem assessor" por extenso — tooltip ali seria eco).
+- **`MunicipalityListAdvisorsControl`** (`src/components/campaign/municipality/…`): envolve o próprio `PopoverTrigger` no `CampaignHoverTooltip` com `openOnTouch={false}`; a coluna não aplica a tooltip genérica nesse papel (senão haveria duas). A composição fica local porque só ela precisa conviver com um Popover.
+- **`formatAdvisorNamesTooltip`** (helper pequeno junto de `MunicipalityAdvisorAvatarStack.tsx`, onde as entradas de avatar já vivem): recebe `MunicipalityAdvisorAvatarEntry[]` e devolve a lista em pt-BR (um nome por linha) ou `null`.
+- **`MunicipalityListTrendControl`** (`src/components/campaign/municipality/…`): extrai o botão-gatilho atual para uma variável e o envolve no `CampaignHoverTooltip` **apenas** quando `trendNote?.trim()` for não vazio (`disabled={open}`, `openOnTouch={false}`, `side="bottom"`, `align="start"`) — a coluna não declara `cellTooltip` para tendência, pela mesma razão da célula de assessores do coordenador (senão haveria duas). Dentro do `PopoverContent`, acima do `Field`, um `` com a nota quando existir; o ` ` permanece como está.
+- **Testes**: unit do helper de nomes (vazio → `null`; 1, 3 e 5 nomes) + unit do contrato da tabela garantindo que uma coluna sem `cellTooltip` renderiza a célula sem wrapper (protege o `overflow-x-visible`/sticky header da lista de municípios contra regressão de layout) + unit em `tests/unit/campaignComponents.unit.spec.ts` (família já pinada) de que o gatilho de tendência ganha a nota como descrição quando ela existe e nenhuma tooltip quando não existe.
+- **Sem migration, sem collection, sem server action.**
+
+## Dependências
+
+- **Suave: B22** (promoção de `MunicipalityHoverTooltip` → `shared/CampaignHoverTooltip`). Quem chegar primeiro faz a promoção; nenhum dos dois bloqueia o outro. Ordem preferida: B22 antes (mesmo appetite, e deixa o primitivo já no lugar).
+- Suaves já entregues: **Pass 2 W1** (colunas como dado em `CampaignTable`), **B19 ✓** (o Popover de assessores e a carteira que dá sentido aos nomes), **E9 ✓** (badge "sem responsável" que a tooltip não deve duplicar), **B9** (edição rápida na célula — é o gatilho que as duas células compartilham), **E4R ✓** (quem populou as notas de tendência em massa; sem ele o hover da tendência explicaria pouco).
+- Reusa sem alterar: `MunicipalityListViewModel.politicalTrendNote` (`src/utilities/municipalityViewModels.ts`) e `advisorNamesById` — nenhuma mudança de loader, `select` ou query.
+- **B17** (seletor de colunas) e **B21** (territórios) são consumidores futuros, não dependências.
+
+## Não escopo
+
+- Copy/tooltip de **header** por coluna — é o **B22**; este item cuida do conteúdo da célula. Em particular, explicar **o que a coluna "Tendência" mede** é B22; aqui só aparece a justificativa daquela linha.
+- Tooltip nas outras colunas de municípios e nas outras 7 listas — capacidade pronta, adoção sob demanda (ver Adiado com gatilho).
+- **Editar a justificativa pela lista** — continua no form de estratégia (`MunicipalityStrategyForm` / `/campanha/municipios//editar`); a Popover da lista é single-field por decisão do B9, e um textarea ali a transformaria em formulário.
+- Autoria/data da tendência na lista (ver Questões em aberto) e qualquer mudança em `recordedBy`/`recordedAt`.
+- Cards mobile de `/campanha/municipios`: já mostram os nomes de assessores por extenso (`names.join(', ')`); a justificativa da tendência chega pelo mesmo Popover do card — nada específico a fazer.
+- Mini-perfil do assessor (e-mail, carteira, contato) — é `/campanha/assessores` (**B19 ✓**).
+- Truncamento inteligente de texto com "…" + tooltip automática por overflow — ver Rabbit holes.
+
+## Rabbit holes
+
+- **Detectar overflow para "só mostrar tooltip quando truncar".** Se alguém "só completar": `ResizeObserver`/medição por célula em 435 linhas × N colunas, re-render em resize, e a tabela deixa de ser server component barata. **Mitigação neste item:** a tooltip é declarada pela coluna, nunca inferida do layout.
+- **Transformar a tooltip em cartão rico (HoverCard) com foto, e-mail e carteira.** Explode em leitura privilegiada de e-mail fora do gate do B19, novo loader e decisão de access. **Mitigação:** decisão travada "só nomes"; caminho de aprofundamento é `/campanha/assessores`.
+- **Tornar as células focáveis "por acessibilidade".** Puxa `role="grid"` + roving tabindex + revisão das 8 listas. **Mitigação:** contrato de que a tooltip é redundante; o texto acessível continua na célula.
+- **Composição Tooltip + Popover no mesmo gatilho.** Se tratada de passagem, dá tap duplo, foco preso ao fechar o Popover e tooltip presa aberta. **Mitigação:** `openOnTouch={false}` + `disabled={open}` nas células editáveis e verificação explícita no critique (abrir/fechar o Popover por teclado sem tooltip órfã).
+- **Sanitizar/normalizar a nota do E4R** (separar a justificativa da nota de proveniência, parsear a célula crua da planilha). Vira parser de planilha dentro da UI. **Mitigação:** exibir cru; qualquer normalização é decisão do seed (E4R), não da lista.
+- **"Já que a nota está no hover, deixa editar por ali."** Vira textarea no Popover, validação de tamanho, conflito com o hidden input e um segundo caminho de escrita para o mesmo campo. **Mitigação:** leitura apenas (Não escopo acima).
+
+## Adiado com gatilho
+
+- **Adotar `cellTooltip` em outras colunas/listas** (Território truncado, dobradinhas por município). Revisitar quando: alguém do time relatar em sessão/R6 não conseguir ler uma célula, ou quando uma coluna nova nascer com conteúdo abreviado.
+- **Justificativa em tooltip nas outras superfícies de tendência** (`MunicipalityStrategyCard`, dossiê). Revisitar quando: alguma delas passar a esconder a nota por densidade — hoje ambas a exibem por extenso.
+- **Affordance visual (sublinhado pontilhado) na célula.** Revisitar quando: a primeira coluna de **texto** adotar a capacidade, ou o critique do R6 registrar falha de descoberta.
+
+## Referências
+
+- `docs/roadmap.md` (Trilha B — "Demais itens abertos", grafo, Janela 1–2, Cortes seguros)
+- `src/components/campaign/shared/CampaignTable.tsx` — `CampaignTableColumn`, render de ``, seams do B17
+- `src/components/campaign/municipality/MunicipalityList.tsx` (~186–337, 450–500) — colunas `advisors` e `trend`, `advisorEntries`, card mobile
+- `src/components/campaign/municipality/MunicipalityAdvisorAvatarStack.tsx` — stack de iniciais + `sr-only` (base da regra de redundância) e `MissingAdvisorBadge`
+- `src/components/campaign/municipality/MunicipalityListAdvisorsControl.tsx` — Popover de edição que a tooltip não pode atrapalhar
+- `src/components/campaign/municipality/MunicipalityListTrendControl.tsx` — gatilho da tendência, Popover de edição rápida e o `trendNote` hoje só em hidden input
+- `src/utilities/municipalityViewModels.ts` — `politicalTrendNote` já no view model da lista (nenhuma mudança necessária)
+- `src/collections/Municipality.ts` — group `politicalTrend` (status/note/auditoria derivada)
+- `src/components/campaign/municipality/MunicipalityStrategyCard.tsx` — onde a nota aparece hoje (referência de copy)
+- `docs/plans/import-planilha-projecao.md` (**E4R ✓**) — origem das notas de tendência (SITUAÇÃO + nota de proveniência)
+- `src/components/campaign/municipality/MunicipalityHoverTooltip.tsx` — primitivo (hover/foco/tap + dismiss por `pointerdown`) a compartilhar
+- `docs/plans/explicacao-colunas-header-listas.md` (**B22**) — decisão irmã no header e a promoção para `shared/`
+- `docs/plans/gerenciar-assessores.md` (**B19**) — carteira de assessores e leitura privilegiada de e-mail (por que ela fica fora da tooltip)
+- AGENTS.md — naming (identificadores em inglês / copy pt-BR), client boundary (`'use client'` só nas ilhas), knip zero após o move
+- `PRODUCT.md` / `DESIGN.md` — Field Desk, clareza sob pressão, edit where you see, anti-goal data-grid
diff --git a/docs/roadmap.md b/docs/roadmap.md
index d013f934..ac17e537 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -1,6 +1,6 @@
# Roadmap — Teqo
-Atualizado em: 2026-07-25 (**Pass 2 ✓** consolidação de engenharia — listas genéricas/B16+ absorvido/B17-B18 triviais/E10 limpo/C8 F4 ✓/D3 consent WhatsApp; **B16+** débitos de escala/DRY pós-simplify do B16 registrados como fill-in; resiliência do build ao global `metadata` ausente no site público; **B16 ✓** filtros no header da lista; **E16 ✓** dossiê do município — aba + print + `budgetNotes` G11; **E9 ✓** fila de alocação — desbloqueia E11, e revisou a fórmula da meta sugerida do E8; **E18 ✓** documentação de conceitos (`/campanha/conceitos`); **B19 ✓** gerenciar assessores; **E8 ✓** conta da cadeira — desbloqueou E9/E10/E12/E13; C12 ✓; C11 absorve escala plano↔demandas; B18; B17; reorder DnD fora de escopo; A11 ✓; B15 ✓; E4R ✓; E17 ✓; B14; janela 1: smoke, R6)
+Atualizado em: 2026-07-25 (**C13** renomear "Plano de Ação" → **"Atividade"** em toda a vertical — pedido de produto (2026-07-25): a palavra que a mesa usa é "atividade"/"agenda" e "plano de ação" sempre precisa ser explicado (o próprio `PRODUCT.md` já chama a superfície de "agenda"); rename **completo** — entidade e labels, `slug: 'activity'`, tabelas/enums/colunas/índices, 61 arquivos (13 componentes, 7 utilities, schemas, actions, access), campo `campaignDemand.actionPlan` → `activity`, rota `/campanha/planos` → `/campanha/atividades` e copy fora da vertical (organizações, demandas, sidebar) — com **migration data-preserving escrita à mão** (`ALTER … RENAME` de tabela/tipo/índice/constraint, idempotente e logada; o diff do Payload geraria DROP+CREATE e apagaria a agenda já cadastrada — a janela de "sem dados reais" que justificou o drop na remodelagem fechou: atividade não tem trava jurídica e a mesa registra assim que o smoke passar); guard estende o describe "banned campaign terminology" de `tests/unit/codebaseConventions.unit.spec.ts` para tabela de termos varrendo `src`+`tests`+`scripts` (compostos + copy + rota; `plan`/`plano` isolado **não** entra — colidiria com os "planos" da Fase 2 white-label); fazer **antes** do onboarding e de **E13**, e obrigatoriamente antes do congelamento (~20/09) por carregar migration; [plano](plans/renomear-plano-acao-para-atividade.md); **B37** chips editáveis de municípios na lista de dobradinhas — coluna "Municípios" de `/campanha/dobradinhas` troca o número morto (`municipalityCount`) por chips com link (município) / território (`MapPinIcon`) e o mesmo Popover+combobox do **B34** (busca por município/TI/ZE, lote de território/zona), mas escrevendo do lado invertido (`municipality.stateDeputies`, sem piso mínimo, lock por município em vez de por dobradinha, sem `assertMunicipalitiesWithinScope` porque `canUpdateMunicipality` já escopa o assessor); reaproveita/promove a mesma lib do B19/B34 (quem chegar primeiro faz a renomeação); mesmo conflito registrado com o pedido literal (toggle "Editar" de tabela inteira) que B28/B31/B32/B34/B36 já resolveram na direção do Popover por célula — decisão marcada como _proposta, a validar com produto_; sem migration, [plano](plans/chips-municipios-lista-dobradinhas.md); **B36** coluna "Lideranças" em `/campanha/dobradinhas` com a mesma UI/mecânica da coluna "Municípios" de `/campanha/assessores` (B19 ✓) — chips + Popover + `Command` + auto-save por delta + "Ver mais…" (clamp de 3 linhas por `ResizeObserver`, necessário aqui porque um deputado popular pode acumular muito mais lideranças do que uma liderança acumula dobradinhas); pedido explícito de não duplicar lógica — reusa **sem alteração** a mutação/schema/lock que o **B31** desenha para o lado inverso da mesma relação `leadership.stateDeputies`, promovendo a célula desse item para um componente compartilhado bidirecional (`LeadershipStateDeputyRelationCell`) em vez de um segundo componente-irmão; corrida de extração com o B31 (quem entrar primeiro paga a peça compartilhada); sem migration, [plano](plans/chips-liderancas-lista-dobradinhas.md); **B34** chips editáveis de municípios na lista de lideranças — coluna "Municípios" de `/campanha/liderancas` ganha chips com link (município) / território (`MapPinIcon`), edição por **Popover disparado por célula** com chips removíveis + combobox `Command` (busca por município/TI/ZE, lote de território/zona), gravação **por delta** direto em `leadership.municipalities` com lock `leadership-municipalities:{id}` (piso de 1 e teto de 30 respeitados) e `revalidatePath` (`liderancas`, `liderancas/[id]`, `municipios/[slug]` dos tocados); promove a lógica pura de `advisorMunicipalityPortfolio.ts`/`loadAdvisorMunicipalityIndex` (**B19 ✓**) para nomes neutros, sem portar a UI com `ResizeObserver`; **conflito registrado com o pedido literal** (pede um toggle "Editar" de tabela inteira, que B28/B31/B32 já travaram como anti-goal nesta mesma tabela — este plano segue o precedente e marca a decisão como _proposta, a validar com produto_); sem migration, [plano](plans/chips-municipios-lista-liderancas.md); **B33** ordenação e filtro no header da lista de dobradinhas — leva B15 ✓/B16 ✓ para `/campanha/dobradinhas` (sort por Nome/Partido, filtro por Partido); consome o header rico compartilhado que **B29** extrai (ou extrai o dele mesmo, se chegar primeiro — mesma corrida de ordem de chegada de B21/B29) — Não escopo do B29 já nomeava esta entrada; contagens derivadas (`municipalityCount`/`leadershipCount`) ficam fora do sort v1, [plano](plans/ordenacao-filtros-lista-dobradinhas.md); **B32** editar Status de apoio na célula da lista de lideranças — clicar na célula abre um Popover com select de `supportStatus`, gravando sozinho por endpoint JSON dedicado (`POST /campanha/liderancas/support-status`, espelho do `expected-votes/`), **sem** o toggle "Editar" de tabela inteira (pedido explícito de produto, mesmo anti-goal do B28/B31); fecha o "Adiado com gatilho" que o próprio B29 já havia registrado para este caso; extrai `isSameOriginRequest` (2º endpoint) e as mensagens seguras do form action da ficha para módulos compartilhados; sem migration, [plano](plans/autosave-status-lista-liderancas.md); **B31** coluna de dobradinhas na lista de lideranças — chips com link + adicionar/remover na própria linha, com busca e gravação automática, na paridade pedida com a coluna "Municípios" de `/campanha/assessores` (B19 ✓) — paridade **da coluna**, não do modo "Editar" de tabela inteira (anti-goal do princípio 3 e do B28); escrita **por delta** com lock (`leadership-state-deputies:{id}`) em vez de replace do array, e `revalidatePath` porque `/campanha/dobradinhas` tem contagens derivadas; sem migration, [plano](plans/dobradinhas-lista-liderancas.md); **B30** convite de completar cadastro por WhatsApp na lista de lideranças — ação ao fim de cada linha de `/campanha/liderancas` reusando `createCampaignInvite`/`kind: 'autopreenchimento'` (mesmo fluxo do detalhe), disabled quando `contact.phone` não normaliza (`normalizeBrazilianPhone`); ícone distinto do WhatsApp de contato do B28 (soft, mesma linha); sem migration/action nova, [plano](plans/convite-whatsapp-lista-liderancas.md); **B29** ordenação e filtro no header da lista de lideranças — leva B15 ✓/B16 ✓ para `/campanha/liderancas` (sort/filtro na URL, popover no header, colunas "Setor" e "Última atualização"), ordenação por nome via join `contact.name` (sem desnormalizar), e é o **3º call site** que dispara a extração do par head/filtro para `shared/` (municípios migrado junto; B21 fecha seu adiado), [plano](plans/ordenacao-filtros-lista-liderancas.md); **B28** e-mail + celular na lista de `/campanha/liderancas` — colunas com copy-on-click + WhatsApp como em `/campanha/assessores` (B19 ✓); edição in-list adiada; sem migration, [plano](plans/email-celular-lista-liderancas.md); **B27** combobox + chips + auto-save no popover de Assessores de `/campanha/municipios` — a lista de checkboxes com botão "Salvar assessores" vira chips removíveis sobre um combobox com busca, gravando **por delta** (endpoint JSON espelho do `expected-votes/`, lock do B19 ✓), [plano](plans/combobox-assessores-lista-municipios.md); **B26** registrar sinal a partir da célula "Último sinal" de `/campanha/municipios` — Popover no desktop e bottom sheet (`Drawer`) no mobile criando `municipalityUpdate` de `kind: 'sinal'` (C12 ✓) sem sair da fila; submit explícito por ser criação atômica multi-campo (exceção nomeada no princípio 4), [plano](plans/registrar-sinal-lista-municipios.md); **fill-in** renomear coluna "Cobertura da meta" → "Cobertura" em `/campanha/municipios` — header curto (padrão A11); glossário/KPI strips intactos; B16 ✓ liberou o nome ao remover "Assessoria", [plano](plans/renomear-coluna-cobertura-meta.md); **B25** link do território na lista de municípios — a célula da coluna "Território" vira link para a linha daquele TI em `/campanha/territorios` (âncora, sem rota de detalhe); dep dura B21, [plano](plans/link-territorio-lista-municipios.md); **B24** auto-save + justificativa editável no popover de Tendência de `/campanha/municipios` — tira o botão "Salvar" que o princípio 4 do `PRODUCT.md` proíbe e fecha o adiado "nota no popover" do B9, [plano](plans/autosave-tendencia-lista-municipios.md); **fill-in** remover coluna "Tipo" de `/campanha/municipios` — município e ZE tratados iguais na coordenação; filtro por Salvador cobre as zonas; drop também do filtro/sort/`?kind=` na URL da lista, schema `kind` intacto, [plano](plans/remover-coluna-tipo-municipios.md); **B23** tooltip de conteúdo nas células das listas — `cellTooltip` em `CampaignTableColumn`, consumidores as colunas "Assessores" e "Tendência" (justificativa do E4R no hover; sem nota, sem tooltip) de `/campanha/municipios`, [plano](plans/tooltip-celulas-listas.md); **fill-in** opções selecionadas no topo nos Popovers de filtro do header — helper puro reutilizável pelas demais listas, [plano](plans/selecionados-no-topo-filtros-header.md); **B22** explicação por coluna no header das listas — tooltip de hover/foco/tap promovido para o sistema de listas compartilhado, [plano](plans/explicacao-colunas-header-listas.md); **B21** página própria dos Territórios de Identidade — rota `/campanha/territorios` com overview + tabela no sistema de listas (sort/filtro no header), sem migration, [plano](plans/pagina-territorios-identidade.md); **B20** Municípios prioritários no Início — atalho ranqueado por déficit + markup sem bullets, [plano](plans/municipios-prioritarios-inicio.md); **Pass 2 ✓** consolidação de engenharia — listas genéricas/B16+ absorvido/B17-B18 triviais/E10 limpo/C8 F4 ✓/D3 consent WhatsApp; **B16 ✓**; **E16 ✓**; **E9 ✓**; **E18 ✓**; **B19 ✓**; **E8 ✓**; C12 ✓; B18; B17; A11 ✓; B15 ✓; E4R ✓; E17 ✓; B14; janela 1: smoke, R6)
Registro canônico dos **próximos** planos e débitos. Histórico de entregas: resumo abaixo + planos em [`docs/plans/`](plans/) + notebook [`.cursor/rules/projects/nucleos-eleitorais.mdc`](../.cursor/rules/projects/nucleos-eleitorais.mdc).
## Âncoras do calendário eleitoral 2026 (Res. TSE 23.760/2026)
@@ -76,6 +76,7 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
- ~~**A11** posição em votos do município (rank absoluto + % da própria votação) + ordenação da lista por votação~~ — **entregue 2026-07-24** (helper `municipalityVoteRank`; baseline + coluna lista; default `?sort=votos` desc; header=`2022`; consome contrato B15) · [plano](plans/ranking-votos-municipio.md)
- ~~**E17** tabela comparativa dos Territórios de Identidade no Início (pedido do candidato) · somas/razões apenas, Metropolitano decomposto (salvaguardas MAUP) · ~1d · Janela 1–2 · primeira fatia de E12 · [plano](plans/tabela-ti-inicio.md)~~ — **entregue 2026-07-24** (`territoryOverview.ts` + `loadTerritoryOverview.ts` + `TerritoryOverviewTable.tsx`; rollup puro client-safe, loader `overrideAccess: true`, Metropolitano decomposto, ordenação client-side; sem migration)
- ~~**C12** registro-fundação~~ — **entregue em código 2026-07-24** (versions, sinais, origem/custo derivado, decisões ex-ante; migration + testes) · [plano](plans/registro-fundacao.md)
+- **C13** renomear "Plano de Ação" → "Atividade" (entidade, código, banco e rotas) — pedido de produto (2026-07-25): a mesa diz "atividade" (ou "agenda") e o nome atual **sempre precisa ser explicado**, custo de tradução recorrente justamente na janela de onboarding; `PRODUCT.md` (§Users, §Product Purpose) já descreve a superfície como "agenda", então a divergência é entre o produto escrito e o implementado. Rename **completo, sem meio-termo**: collection `actionPlan` → `activity` (labels "Atividade"/"Atividades"), tabelas `action_plan*` → `activity*` + 3 enums + `campaign_demand.action_plan_id` e `payload_locked_documents_rels.action_plan_id` + ~30 índices/constraints, 61 arquivos e ~820 ocorrências de identificador (13 componentes `ActionPlan*` → `Activity*`, 7 utilities `actionPlan*` → `activity*`, `access/actionPlans.ts`, `lib/schemas/actionPlan.ts`, `actions/actionPlan.ts`), rota `/campanha/planos` → `/campanha/atividades` (interna e autenticada — **sem** redirect legado, que seria uma rota fantasma a allowlistar) e a copy fora da vertical (`Organization`, `CampaignDemand`, `/campanha/organizacoes`, sidebar "Planos" → "Atividades"). **Migration data-preserving escrita à mão** (`ALTER … RENAME`, guardas `IF EXISTS`, contagem logada, `down()` simétrico; snapshot `.json` gerado é mantido e só o SQL é substituído — padrão de `rename_tag_visible_to_hidden` + reconcile da remodelagem): o diff do Payload geraria DROP+CREATE, e ao contrário de 2026-07-23 **já não vale supor banco vazio** — atividade não depende do lote jurídico, então a mesa a preenche assim que o smoke passar. **Guard** (pedido explícito): o describe "banned campaign terminology" de `tests/unit/codebaseConventions.unit.spec.ts` — que hoje mantém Praça/Núcleo fora de `src` — vira **tabela de termos** e passa a varrer `src`+`tests`+`scripts` (migrations continuam fora, história congelada; o próprio spec entra na allowlist porque cita os literais); banimento = compostos (`actionPlan|ActionPlan|ACTION_PLAN|action_plan|action-plan`), copy ("plano/planos de ação") e a rota antiga — **não** `plan`/`plano` isolado, que colidiria com os "planos" de assinatura da Fase 2 white-label. Zero mudança de comportamento, access, campo ou fluxo; PR mecânico e isolado (o polish visual da vertical continua no **R6**) · ~1–1,5d · Janela 1 (antes do onboarding; **antes de E13**, que constrói dentro desta vertical; e antes do congelamento ~20/09 por carregar migration) · Impeccable B · sem deps duras · cortável (mas cortar cedo ou nunca) · [plano](plans/renomear-plano-acao-para-atividade.md)
- **A6** dobradinha 2026 automática quando o TSE publicar candidaturas · gatilho externo: pós-15/08 · camada de insight sobre o registro operacional `stateDeputy` (M4) · alimenta **E13** · [plano](plans/insight-dobradinha-2026.md)
- **B5 F2–F3** cache CLI compartilhado + factory mun/TI (scripts continuam) · [plano](plans/escala-dry-pos-b2.md)
- **B8 F2** polígonos dos Municípios-zona de Salvador (ZE 1–19; Camaçari saiu do escopo na M1 — município inteiro): F1 catálogo zona→bairros entregue (hoje Salvador-only); falta prep do catálogo (~½ dia) + dissolve IBGE/malha → TopoJSON no mapa · Janela 3 · cortável · [plano](plans/poligonos-pracas-zona.md)
@@ -87,8 +88,25 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
- **D5** inbox da própria sessão → rascunhos (`municipalityUpdate`/demanda) com humano no loop · depende de D3 · [plano](plans/whatsapp-sugestao-atualizacoes.md)
- **R6** critique/polish visual da vertical remodelada (ciclo /impeccable completo por superfície; smoke visual coordenador feito em 2026-07-21) · absorve os débitos de produto/UX remanescentes de FD2 ([field-desk-ux-pos-critique.md](plans/field-desk-ux-pos-critique.md)): glossário inline (O3 — hipótese ainda sem evidência), triagem em lote, empty states de coordenador · gatilho: antes de 16/08
- **B14** município mais próximo (geolocalização → atalho no Início staff) · pede permissão **1× por sessão** se ainda não concedida; matching client-side sobre centroides IBGE; Salvador multi-zona → lista filtrada até B8 F2 · ~1d · Janela 1–2 · sem deps duras · cortável · [plano](plans/municipio-mais-proximo.md)
+- **B20** Municípios prioritários no Início — a seção em `/campanha` deixa de mentir por omissão (hoje `.slice(0, 6)` sem sort sobre ~50 `priority=alta` do E4R): atalho das 8 com maior déficit de cobertura (E9/`central`) + contagem + “Ver todas” → `?priority=alta`; markup `list-none` (bullets hoje) · ~0,5d · Janela 1 · sem deps duras (E4R/E8/E9 ✓) · cortável · [plano](plans/municipios-prioritarios-inicio.md)
+- **B21** página própria dos Territórios de Identidade — rota staff `/campanha/territorios` com overview + tabela dos 27 TIs no sistema de listas (colunas como dado em `CampaignTable`, sort no header via URL B15, filtro em Popover B16, busca/chips/footer compartilhados); reusa o rollup do **E17 ✓** (`territoryOverview.ts`/`loadTerritoryOverview.ts`), Metropolitano decomposto, linha → `?region=`; painel do Início vira variante compacta com “Ver todos” (mesmas definições de coluna); **E12** passa a pousar suas métricas aqui em vez de abrir tela nova; sem migration/collection/action · ~1–1,5d · Janela 1–2 · Impeccable C · soft: E12/B17/B13 · cortável · [plano](plans/pagina-territorios-identidade.md)
- ~~**B15** ordenar lista de municípios pelo header da coluna~~ — **entregue 2026-07-24** (`?sort=`/`?dir=` na URL + clique no header (desktop) / select compacto (mobile); ordenação global no filtrado; **A11** consome o contrato (key `votos`)) · [plano](plans/ordenacao-colunas-lista-municipios.md)
- ~~**B16** filtros no header das colunas da lista de municípios~~ — **entregue 2026-07-24** (funil+Popover no `TableHead` ao lado do sort B15; barra slim busca+resumo+Limpar; mobile selects empilhados; optimistic + busca no Popover de TI) · [plano](plans/filtros-no-header-lista-municipios.md)
+- **B22** explicação por coluna no header das listas — pedido de produto (2026-07-25): tooltip no hover de cada header de `/campanha/municipios` dizendo o que a coluna mede, **implementado no componente de header** para reuso. Hoje a capacidade existe pela metade (prop `tooltip` do `MunicipalitySortableHead`, usada em 3 de 9 colunas; primitivo preso ao domínio `municipality`; `CampaignTableHead` sem prop nenhuma): promover `MunicipalityHoverTooltip`→`shared/CampaignHoverTooltip`, prop `description` nos dois heads, copy das colunas de municípios com `oneLiner` do **E18 ✓** como fonte única nas métricas do E8; sem migration/action; dá evidência datada para o débito **O3**/glossário inline do R6 · ~0,5d · Janela 1–2 · Impeccable B · soft: B15 ✓/B16 ✓/E18 ✓ · cortável · [plano](plans/explicacao-colunas-header-listas.md)
+- **B23** tooltip de conteúdo nas células das listas — dois pedidos de produto (2026-07-25): (a) hover na célula de "Assessores" em `/campanha/municipios` mostra os nomes dos assessores do município (hoje só há até 3 avatares de **iniciais** + `sr-only`; do 4º em diante nem avatar); (b) hover na célula de "Tendência" mostra a **justificativa** registrada (`politicalTrend.note`, semeada em massa pelo **E4R ✓** — hoje já chega na célula, mas só como ` `, e só é legível abrindo o detalhe), **sem tooltip quando não há nota**. Ambos **implementados como capacidade genérica da célula** para reuso em outros campos/páginas: `cellTooltip?: (row) => ReactNode` em `CampaignTableColumn`, envolvido pela `CampaignTable` no primitivo compartilhado (`CampaignHoverTooltip` — promovido no **B22** ou aqui, o que vier primeiro); sem novos tab stops (tooltip é redundante; texto acessível segue na célula) e, nas células que já são gatilho de Popover de edição rápida (assessores do coordenador, tendência), `openOnTouch={false}` + `disabled={open}` para não roubar o tap nem sobreviver atrás do Popover (B9/B19 ✓) — em touch a informação aparece dentro do próprio Popover (a nota da tendência entra ali como leitura, acima do select); sem migration/action/query nova · ~0,75d · Janela 1–2 · Impeccable B · soft: B22 (primitivo compartilhado), E4R ✓ (as notas) · cortável · [plano](plans/tooltip-celulas-listas.md)
+- **B24** auto-save + justificativa no popover de Tendência da lista de municípios — pedido de produto (2026-07-25): o popover da coluna "Tendência" perde o botão **"Salvar"** (grava sozinho: status na hora, justificativa com debounce que **cancela o timer anterior**, flush no blur/fechamento) e ganha o campo para **editar a justificativa** (`politicalTrend.note`, hoje hidden input na lista e só editável em `/[slug]/editar`). Fecha duas dívidas datadas: o anti-goal literal do princípio 4 do `PRODUCT.md` ("Save buttons on Popovers and quick-edit cells") — a célula vizinha da mesma linha (votos estimados) já grava sozinha — e o "nota de tendência no popover, gatilho: feedback de uso" adiado no **B9 ✓**. Persistência por endpoint JSON próprio (`POST /campanha/municipios/political-trend`, espelho do `expected-votes/`), **sem revalidar** a lista de 435 linhas por gravação; `setMunicipalityPoliticalTrend` e o access ficam intactos; sem migration/collection/Consent · ~0,5d · Janela 1–2 · Impeccable B · soft: B23 (se vier antes, a nota read-only do Popover vira o textarea) · cortável · [plano](plans/autosave-tendencia-lista-municipios.md)
+- **B25** link do território na lista de municípios — pedido de produto (2026-07-25): na coluna "Território" de `/campanha/municipios` o nome do TI vira link para **a página daquele território**. Hoje a célula é texto morto (`cell: (m) => m.region`) e a navegação é assimétrica: o painel do Início (**E17 ✓**) já linka TI → `/campanha/municipios?region=`, mas do município não se chega ao território. Destino = a página do **B21** ancorada na linha do TI (`/campanha/territorios#ti-` + `scroll-mt` e `target:` no precedente de `/campanha/conceitos`), **não** rota de detalhe `/campanha/territorios/` (Não escopo do B21, sem evidência nova: seria a linha do rollup + a lista filtrada, que já existem) nem `?region=` (tabela de uma linha destrói a comparação que é o job da tela). Âncora derivada por helper puro (`territoryAnchorId` sobre `slugify`), consumida pelas duas pontas e pinada por teste — TI não ganha slug persistido; sem migration/collection/action/query nova · ~0,25d · Janela 1–2 · Impeccable B · **dep dura: B21** · soft: B17 (coluna ocultável) · E12 é gatilho de revisitação da decisão "âncora vs rota de detalhe", não dependência · cortável · [plano](plans/link-territorio-lista-municipios.md)
+- **B26** registrar sinal a partir da célula "Último sinal" da lista de municípios — pedido de produto (2026-07-25): clicar na célula de frescor abre um **Popover** (desktop) / **bottom sheet `Drawer`** (mobile) que cria um `municipalityUpdate` de `kind: 'sinal'` (**C12 ✓**: texto + tipo + fonte + `triangulated`) sem sair da lista. Fecha o laço da fila: o **E9 ✓** diz onde está frio ("há 34 dias", âmbar a partir de 21) e hoje a única saída é abrir `/[slug]?tab=updates` e perder o lugar na varredura — é o último readout morto da faixa staff da linha (assessores/tendência/votos já editam in loco desde B9/B19 ✓). **O submit continua explícito** ("Registrar sinal"): é criação **atômica multi-campo** de registro **imutável** para o staff (`canMutateMunicipalityUpdate` = só admin), exatamente a exceção nomeada no princípio 4 do `PRODUCT.md` — auto-save aqui criaria registros inválidos ou lixo permanente no feed, ao contrário do **B24** (campo único na mesma linha). Grava por server action + `revalidateMunicipalityListPaths` (o rótulo é **derivado**, `max(lastUpdateAt, lastPledgeAt)` — otimismo local seria o anti-goal do princípio 3, e o detalhe/dossiê **E16 ✓** precisam ver a linha nova); variante mobile escolhida pela divisão de breakpoint que a lista já tem (cards `md:hidden` × tabela `hidden md:block`), **não** por `useIsMobile`; sem migration/collection/Consent/endpoint JSON · ~0,75d · Janela 1–2 · Impeccable B · soft: B24 (contrato vizinho deliberadamente diferente), B23 (se o `cellTooltip` chegar nesta coluna, mesmo `openOnTouch={false}`/`disabled={open}`) · cortável · [plano](plans/registrar-sinal-lista-municipios.md)
+- **B27** combobox + chips + auto-save no popover de Assessores da lista de municípios — pedido de produto (2026-07-25): na coluna "Assessores" de `/campanha/municipios`, o popover de edição rápida (**B9 ✓**) troca a **lista de checkboxes + botão "Salvar assessores"** por (a) **chips removíveis** dos assessores já atribuídos acima de (b) um **combobox** com busca acento-insensível e teclado, (c) gravando **na hora** a cada atribuição/remoção, com UI otimista. Três dívidas datadas num item só: o botão é o anti-goal literal do princípio 4 do `PRODUCT.md` (a célula vizinha da mesma linha já grava sozinha); a lista de checkboxes não escala com as contas semeadas pelo **E4R ✓**; e a seleção atual só se lê como iniciais no gatilho — o mesmo par entidade↔carteira já é chips + busca em `/campanha/assessores` (**B19 ✓**). Gravação **por delta** (`municipalityId`+`advisorId`+`assigned`) via endpoint JSON próprio espelhando `expected-votes/` — **sem revalidar** 435 linhas por clique e sem o replace do array (que sob auto-save apaga a edição concorrente); reusa o lock `municipality-advisors:{id}` e o cálculo puro do B19 (`nextAdvisorIdsAfterMembership` sobe para `src/lib/`), mas **não** o record daquela tela (`assertTargetAdvisor` recusa coordenador/candidato e o próprio ator — legais aqui). Combobox = `Command` já em uso, não primitivo novo; gatilho da célula intacto (contrato de tooltip do B23); UI segue coordenador-only (candidato = adiado com gatilho); sem migration/collection/Consent/query nova · ~0,75d · Janela 1–2 · Impeccable B · soft: B24 (mesmo padrão de endpoint; quem chegar primeiro extrai `isSameOriginRequest`), B23 (tooltip no mesmo gatilho — os chips melhoram o fallback de toque que aquele plano presumia), B26 (contrato deliberadamente diferente na mesma linha), B19 ✓/B16 ✓/B9 ✓/E4R ✓/E9 ✓ · cortável · [plano](plans/combobox-assessores-lista-municipios.md)
+- **B28** e-mail + celular na lista de lideranças — pedido de produto (2026-07-25): em `/campanha/liderancas` adicionar colunas **E-mail** e **Celular** com o **mesmo funcionamento de leitura** da tabela de assessores (**B19 ✓**): clique copia (toast), “—” se vazio, celular com máscara BR, ícone WhatsApp (`wa.me`) por linha quando o número normaliza. Hoje o loader já traz `phone` no view model e o e-mail só no detalhe — a lista mostra Nome · Status · Municípios · Organizações · Acesso e obriga abrir a ficha para contactar. Extrair o primitivo de copy (+ helper WhatsApp de linha) para `shared/` no mesmo PR (2º call site do B19); `CampaignTable` permanece (não clonar `AdvisorsTable`). **Fora:** toggle “Editar” / debounce in-list (Contact update + locks = Adiado no plano); sem migration/collection/Consent/action de escrita · ~0,5–0,75d · Janela 1 · Impeccable B · soft: B19 ✓, B30 (mesma linha — ícone distinto) · cortável · [plano](plans/email-celular-lista-liderancas.md)
+- **B30** convidar para completar cadastro por WhatsApp na lista de lideranças — pedido de produto (2026-07-25): ação ao fim de cada linha de `/campanha/liderancas` disparando o **mesmo** fluxo que já existe no detalhe (`/campanha/liderancas/[id]` → `LeadershipInviteButtons`, `kind: 'autopreenchimento'`): gera `campaignInvite` (token único, 7 dias) via `createCampaignInvite` e mostra a âncora `wa.me` com a mensagem pronta, num `Popover` compacto (two-step reveal — sem `window.open` automático, que quebra com popup blocker após o `await`). Botão **disabled** quando `contact.phone` não passa em `normalizeBrazilianPhone` (mesmo critério de `AdvisorsTable`/B19 ✓), com `aria-label` explicando o motivo. Distinto do WhatsApp de contato do **B28** (que é atalho de chat sem token) — ícone diferente para não ambiguar as duas ações na mesma linha; nenhum dos dois bloqueia o outro. Reusa 100% da action/collection/access existentes; sem migration, sem endpoint, sem mudança de access · ~0,25–0,5d · Janela 1 · Impeccable B · soft: B28 (coordenar ícone na mesma linha) · cortável · [plano](plans/convite-whatsapp-lista-liderancas.md)
+- **B29** ordenação e filtro no header da lista de lideranças — pedido de produto (2026-07-25): levar para `/campanha/liderancas` o que a lista de municípios ganhou em **B15 ✓** (ordenar clicando no header, `?sort=`/`?dir=` na URL) e **B16 ✓** (filtro multi-seleção em Popover no header, com busca, chips e "Limpar"). Hoje o estado da URL é só `q`+`page` e a ordem é fixa em `-updatedAt` — que, com o seed do **E4R ✓**, é a ordem em que a planilha gravou as fichas. Filtros v1 = **as colunas visíveis** (Status de apoio, Setor, Município com facet no escopo do ator, Acesso ao app), entrando as colunas "Setor" e "Última atualização" (ambas já no view model, hoje não renderizadas) para não haver ordenação invisível; ordenação **no banco**, incluindo nome por join `contact.name` (o adapter resolve caminho pontilhado em `buildOrderBy`) — **sem** desnormalizar nome na `leadership` (segunda fonte de verdade de pessoa é anti-goal do AGENTS; se o join não sair, corta-se o sort por nome, não a normalização). Corrige de passagem o teto mudo de `limit: 200` na pré-resolução de contatos da busca. É o **3º call site** do header rico (municípios ✓ · **B21** · este), o gatilho que o B21 registrou: extrai `CampaignSortableHead` + `CampaignHeaderFilterPopover` para `shared/` (só chrome — estado/optimistic/hrefs ficam nos wrappers de domínio) e migra municípios no mesmo PR; dá a B22/B23 um único lugar onde pousar. Sem migration/collection/Consent/action · ~1,5–2d · Janela 1–2 · Impeccable B · soft: B21 (quem entrar 2º duplica; B29 migra os três), B28 (mesma tabela — colunas de contato ficam sem head rico), B22/B23 (primitivo), B17/B18 (colunas já são dado; URL nasce salvável), E14 (níveis pousam aqui) · cortável · [plano](plans/ordenacao-filtros-lista-liderancas.md)
+- **B31** coluna de dobradinhas na lista de lideranças — pedido de produto (2026-07-25): em `/campanha/liderancas` adicionar a coluna **Dobradinhas** com as mesmas funcionalidades da coluna "Municípios" da tabela de assessores (**B19 ✓**): chips clicáveis (→ `/campanha/dobradinhas/`), remover chip, buscar e adicionar por typeahead, gravação automática otimista. O dado já chega e é descartado (`LeadershipRowViewModel.stateDeputyNames`, como `sector`/`phone` antes de B28/B29), e o **E4R ✓** semeou dobradinha só em `municipality.stateDeputies` — as fichas nasceram vazias e hoje só se preenchem abrindo uma a uma o `RelationMultiSelect` do detalhe e clicando "Salvar", exatamente o trabalho de mesa do onboarding. **Paridade é da coluna, não do modo:** o toggle "Editar" de tabela inteira dos assessores existe lá porque todas as colunas daquela tela são editáveis; trazê-lo para cá é o anti-goal literal do princípio 3 do `PRODUCT.md` (spreadsheet / inputs montados em toda linha) já travado no **B28** — a edição fica por célula (Popover, contrato de B9 ✓/B24/B26/B27; typeahead inline é troca de container no craft, não de contrato). Escrita **por delta** (`leadershipId`+`stateDeputyId`+`assigned`) em transação com lock `leadership-state-deputies:{id}` e teto de 20 — **não** o replace de array do `updateLeadershipInternal` (sob auto-save, dois atores na mesma ficha se sobrescrevem); ao contrário do B24/B27, aqui vale `revalidatePath` (25 linhas, e `/campanha/dobradinhas` tem `leadershipCount` derivado + a ficha do deputado lista lideranças). Catálogo entregue **uma vez por tabela** via provider cliente (props por célula multiplicariam o payload por linha). Access inalterado (`canManageLeadership` escreve, `canReadStateDeputy` lê); sem migration/collection/Consent/mudança de URL · ~1d · Janela 1–2 · Impeccable B · soft: B27 (mesma peça — chips + combobox + delta; extração para `shared/` só no 3º call site), B19 ✓ (padrão e espelho do cálculo), B28/B29/B30 (mesma tabela), B17 (a tabela chega a 8–10 colunas), E4R ✓ · cortável · [plano](plans/dobradinhas-lista-liderancas.md)
+- **B32** editar Status de apoio na célula da lista de lideranças — pedido de produto (2026-07-25): clicar na célula "Status" de `/campanha/liderancas` abre um Popover com `NativeSelect` de `supportStatus` (as 4 opções, mesmo Badge como trigger), gravando sozinho — **sem** botão "Salvar" e **sem** depender do toggle "Editar" de tabela inteira (`AdvisorsTable`/**B19 ✓**), anti-goal nomeado explicitamente no pedido e já travado no **B28**/**B31** para esta mesma tabela. O campo já é editável na ficha (`LeadershipInternalForm`) e o schema já trata `supportStatus` como opcional isolado (`leadershipInternalUpdateSchema`) — falta só o atalho pontual. Gravação por **endpoint JSON dedicado** (`POST /campanha/liderancas/support-status`, espelho do `expected-votes/`), reusando `updateLeadershipInternal` (`{ id, supportStatus }`, `overrideAccess: false`), **sem `revalidatePath`** (25 linhas, e um futuro sort/filtro por Status do B29 só reconcilia na próxima navegação, mesma escolha do B24). Fecha o "Adiado com gatilho" que o próprio **B29** já havia registrado para exatamente este caso. É o **2º endpoint JSON** da campanha — extrai `isSameOriginRequest` para `src/utilities/` (compartilhado com o futuro `expected-votes`/B24/B27) e as mensagens seguras do form action de `/liderancas/[id]` para um módulo dedicado. Sem migration/collection/Consent · ~0,4–0,5d · Janela 1 · Impeccable B · soft: B24 (mesma família de decisão — guard CSRF), B29 (gatilho fechado), B31 (mesma tabela, célula vizinha) · cortável · [plano](plans/autosave-status-lista-liderancas.md)
+- **B33** ordenação e filtro no header da lista de dobradinhas — pedido de produto (2026-07-25): levar para `/campanha/dobradinhas` o que a lista de municípios ganhou em **B15 ✓** (ordenar clicando no header, `?sort=`/`?dir=` na URL) e **B16 ✓** (filtro em Popover no header, com busca, chips e "Limpar"), **sem duplicar código** — reusando a lógica e os componentes já construídos para essa família de listas. Hoje o estado da URL é só `q`+`page` e a ordem é fixa em `sort: 'name'` sem `dir` (`parseStateDeputyListParams`/`loadStateDeputyListPageData` em `src/utilities/stateDeputyData.ts`). V1 = ordenar por Nome (default) e Partido (campo indexado real) e filtrar por Partido (multi, facet dos valores presentes + "Sem partido"); `municipalityCount`/`leadershipCount` são contagens **derivadas** em memória (duas queries auxiliares), não campos de banco, então ficam fora do sort/filtro nesta entrega (agregado SQL seria outro item). **Zero segunda cópia do chrome de head/filtro:** este item consome `shared/CampaignSortableHead`/`CampaignHeaderFilterPopover` — a extração que o plano do **B29** já desenhou e cujo Não escopo nomeia explicitamente "`dobradinhas` … cada uma entra por pedido próprio"; se nenhum dos dois (B21/B29) tiver extraído ainda quando este item for implementado, a extração acontece aqui, seguindo o mesmo contrato desenhado no plano do B29 (mesma corrida de ordem de chegada). Módulo de URL próprio (`stateDeputyListUrl.ts`) e de filtros (`stateDeputyListFilters.ts`) espelhando `municipalityListUrl.ts`/`leadershipListUrl.ts` sobre `campaignListUrl.ts`, sem importar os outros domínios. Sem migration/collection/Consent/action de escrita · ~0,5–0,75 dia se o chrome compartilhado já existir, ~1–1,5 dia se este item extrair · Janela 1–2 · Impeccable B · soft: B29 (mesma corrida de extração), B21 (idem), B15 ✓/B16 ✓ (contrato a espelhar) · cortável · [plano](plans/ordenacao-filtros-lista-dobradinhas.md)
+- **B34** chips editáveis de municípios na lista de lideranças — pedido de produto (2026-07-25): em `/campanha/liderancas`, a coluna "Municípios" (hoje `row.municipalityNames.join(', ')`, texto morto sem link) ganha **chips**: cada município navega para `/campanha/municipios/[slug]`, um chip de território (`MapPinIcon`) aparece quando a liderança cobre o TI inteiro, e um ícone de edição ao fim da célula abre um **Popover** com os chips já atribuídos (removíveis) sobre um combobox `Command` com busca acento-insensível por município/território/ZE — adicionar/remover grava **na hora**, sem "Salvar", incluindo lote de território/ZE inteiro (paridade explicitamente pedida com `/campanha/assessores`, **B19 ✓**). **⚠️ Conflito registrado com o pedido literal:** o pedido pede um **botão de "modo de edição"** de tabela inteira, espelhando o toggle de `AdvisorsTable` — mas essa **mesma tabela** já travou a decisão oposta três vezes no mesmo dia (**B28**, **B31**, **B32**: "paridade é da coluna, não do modo... toggle Editar de tabela inteira é o anti-goal literal do princípio 3"). Este plano segue o precedente (Popover por célula, não toggle) e marca a decisão como _proposta — validar com produto_ antes de implementar. Diferença estrutural do B19: o vínculo mora no próprio `leadership.municipalities` (`hasMany`, piso de 1 via hook `requireAtLeastOneMunicipality`, teto de 30), não numa reverse relationship que pode ir a zero — mutação por **delta** direto no documento da liderança (não replace via `updateLeadershipInternalRecord`, que validaria o array inteiro contra o escopo do assessor e quebraria lideranças cross-boundary), lock único `leadership-municipalities:{id}` (ao contrário do B19, que trava por município), piso de 1 desabilitado no cliente + recusado no servidor como defesa em profundidade, sugestões de busca restritas ao escopo do assessor enquanto os chips já atribuídos usam o catálogo completo. Promove `advisorMunicipalityPortfolio.ts`/`loadAdvisorMunicipalityIndex` (**B19 ✓**) para nomes neutros (`municipalityPortfolio.ts`/`loadMunicipalityPortfolioIndex`) **sem** portar a UI de `AdvisorMunicipalityCell` (o `ResizeObserver` resolve um problema — empacotar chips numa célula sempre-visível — que não existe dentro de um Popover); catálogo entregue uma vez por tabela via provider cliente. `revalidatePath` (não endpoint JSON como o B27): a lista tem 25 linhas, não 435, e há derivados a jusante (`/campanha/liderancas/[id]`, e `/campanha/municipios/[slug]` via `MunicipalityLeadershipsPanel`/dossiê **E16 ✓**). Sem migration/collection/Consent/mudança de URL · ~1–1,25d · Janela 1–2 · Impeccable B · soft: B19 ✓ (lib de origem), B27 (mesma peça de célula), B31 (mesma tabela e mesma decisão de container — extração de editor de chips genérico só no 3º call site), B28/B29/B30/B32 (mesma tabela), B17 (tabela cresce), E16 ✓ (painel do município a revalidar) · cortável · [plano](plans/chips-municipios-lista-liderancas.md)
+- **B37** chips editáveis de municípios na lista de dobradinhas — pedido de produto (2026-07-25): em `/campanha/dobradinhas`, a coluna "Municípios" (hoje `row.municipalityCount`, um número sem nome) ganha o **mesmo tratamento** que a coluna homônima de `/campanha/assessores` (**B19 ✓**) e que o **B34** acabou de desenhar para `/campanha/liderancas`: chips com link para o município, chip de território (`MapPinIcon`) quando a dobradinha cobre o TI inteiro, e um ícone de edição na célula abrindo um **Popover** com os chips já atribuídos (removíveis) sobre um combobox `Command` (busca por município/TI/ZE, lote de território/zona) — gravação **na hora**, sem "Salvar". **⚠️ Mesmo conflito com o pedido literal já registrado quatro vezes no mesmo dia** (B28/B31/B32/B34): o pedido pede o toggle "Editar" de tabela inteira de `AdvisorsTable`; este plano segue o precedente e fica com o Popover por célula, marcado como _proposta — validar com produto_. Diferença estrutural do B34: aqui o vínculo mora do **lado errado** para reaproveitar aquele desenho — `municipality.stateDeputies` (reverse relationship, não `stateDeputy.municipalities`), sem piso mínimo (uma dobradinha pode não cobrir nenhum município ainda) e sem `assertMunicipalitiesWithinScope` (o próprio `canUpdateMunicipality` do Payload já escopa o assessor); escrita por **delta** no município tocado com lock **por município** (`municipality-state-deputies:{id}`, não por dobradinha), mesmo padrão do B19, reaproveitando o teto de 20 já existente em `stateDeputiesArraySchema`. Promove a mesma lib pura do B19/B34 (`advisorMunicipalityPortfolio.ts` → `municipalityPortfolio.ts`) — quem entre B34 e este chegar primeiro faz o `git mv`, o outro só importa; os três (B31+B34+este) juntos são o gatilho já registrado para extrair um `RelationshipChipEditor` agnóstico. `revalidatePath` (dobradinhas, `[slug]`, município tocado) — sem migration/collection/Consent/mudança de URL · ~0,75–1d · Janela 1–2 · Impeccable B · soft: B34 (mesma promoção de lib + mesma decisão de container), B31 (mesma família de pedido, outra tabela), B19 ✓ (origem da lib e do cálculo de próximo array), B33 (mesma tabela — ordenação/filtro no header), E4R ✓ (semeou dobradinha só do lado do município — este item é o preenchimento em lote que faltava) · cortável · [plano](plans/chips-municipios-lista-dobradinhas.md)
+- **B36** coluna de lideranças na lista de dobradinhas (espelho da coluna Municípios de Assessores) — pedido de produto (2026-07-25): em `/campanha/dobradinhas`, a coluna "Lideranças" (hoje `row.leadershipCount`, uma contagem sem nomes) ganha as **mesmas funcionalidades e UI** da coluna "Municípios" da tabela de assessores (**B19 ✓**): chips nomeados linkando para `/campanha/liderancas/[id]`, adicionar/remover na própria linha via Popover + busca (`Command`) sem botão "Salvar", gravação **por delta** com estado otimista, e clamp de 3 linhas + "Ver mais…" (`ResizeObserver`, portado do `AdvisorMunicipalityCell`) — necessário aqui porque, ao contrário do **B31** (a mesma relação vista do lado da liderança, onde poucos chips bastam), um deputado popular pode acumular dezenas de lideranças engajadas. É **a mesma aresta `leadership.stateDeputies`** que o B31 edita, só do lado oposto: o pedido explícito de **não duplicar lógica** é resolvido reusando **sem alteração** a mutação/schema/lock que o B31 desenha (`setLeadershipStateDeputyMembership`, teto `MAX_LEADERSHIP_STATE_DEPUTIES`=20, lock `leadership-state-deputies:{id}`) e promovendo a célula de chips do B31 para um único componente compartilhado bidirecional (`LeadershipStateDeputyRelationCell`, `direction: 'fromStateDeputy' | 'fromLeadership'`) em vez de um segundo componente-irmão fazendo a mesma coisa. Catálogo de busca novo (`loadLeadershipOptions`, espelha `loadStateDeputyOptions`) via `overrideAccess: false` (escopo do assessor já resolvido pelo access de `leadership`); `stateDeputyData.ts` troca `leadershipCount` por `leaderships: {id,name}[]` na mesma query batch já existente (mais `select`, não uma segunda consulta). Sem migration/collection/Consent/mudança de URL · ~0,5–0,75d se o B31 já tiver aterrissado (só a direção nova + o loader), ~1–1,5d se **B36** entrar primeiro (paga a extração da célula/action compartilhadas) · Janela 1–2 · Impeccable B · soft (corrida de extração): **B31** — quem entrar primeiro paga a peça compartilhada, o outro consome; B19 ✓ (padrão de origem), E4R ✓ (fichas vazias dos dois lados), B33 (mesma tabela) · cortável · [plano](plans/chips-liderancas-lista-dobradinhas.md)
- **B17** seletor de colunas (mostrar/ocultar) na lista de municípios — Popover “Colunas” + `localStorage`; desktop only; `name` obrigatória; **Pass 2 W1 deixou trivial:** colunas já são dado (`CampaignTableColumn.id`/`mandatory`/`defaultVisible`); soft: B15 ✓ (ids) / B16 ✓ (barra slim); prepara viewport para **E9** · ~0,5–1d · Janela 1–2 · cortável · [plano](plans/seletor-colunas-lista-municipios.md)
- **B18** filtros salvos na lista de municípios — nomear o estado URL atual (`localStorage`); **Pass 2 W1 deixou trivial:** serialização canônica pronta e congelada em `municipalityListUrl.ts`; acesso rápido de 2º nível sob Municípios no sidebar (hover desktop / expand mobile; sticky só no filtro salvo ativo) · ~1–1,5d · Janela 1–2 · soft: B16/B17 (barra) · cortável · [plano](plans/filtros-salvos-municipios.md)
- ~~**B19** gerenciar assessores — `/campanha/assessores` (lista + novo + detalhe): criar/editar contas `advisor`, ver/atribuir municípios, reenviar link de senha; **só Coordenador Geral e Candidato** (`isCampaignUnrestricted`); alinha access de update/phone do `candidate`~~ — **entregue 2026-07-24** (também alinha atribuição `municipality.advisors` a unrestricted; e-mail na lista via leitura privilegiada; auto-save da carteira) · [plano](plans/gerenciar-assessores.md)
@@ -96,7 +114,10 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
### Fill-ins abertos
+- **Renomear coluna Cobertura da meta → Cobertura** em `/campanha/municipios` — pedido de produto (2026-07-25): header curto na fila (padrão A11 `2022`); definição no hover (**B22**) / glossário (**E18 ✓**). Só lista (header + card mobile + label de sort `deficit`); strips/card/conceitos mantêm o nome longo da métrica. Soft: **B16 ✓** (saiu "Assessoria", liberou o nome) · ~0,25d · Impeccable B · cortável · [plano](plans/renomear-coluna-cobertura-meta.md)
+- **Remover coluna Tipo** em `/campanha/municipios` — pedido de produto (2026-07-25): a coluna "Tipo" (Município × Zona eleitoral) não informa a coordenação (ambos tratados iguais); filtrar ZE = filtrar Salvador (19 slugs). Remove coluna + filtro/sort UI + param `?kind=` do contrato URL da lista; **schema/catálogo `kind` intacto** (mapa, B8, dossiê) · ~0,25–0,5d · Impeccable B · soft: B15 ✓/B16 ✓ (header); B17 ganha uma coluna a menos · cortável · [plano](plans/remover-coluna-tipo-municipios.md)
- **Ícone de prioridade na lista** em `/campanha/municipios` — trocar Badge “Prioritária” por ícone Flag + tooltip “Município prioritário” (hover) na coluna do nome · ~0,25d · Impeccable B · [plano](plans/icone-prioridade-lista-municipios.md)
+- **Selecionados no topo nos filtros do header** em `/campanha/municipios` — nos Popovers de filtro (B16 ✓) as opções já marcadas sobem para o topo (ordem congelada enquanto o Popover está aberto; divisor de hairline), para desmarcar sem caçar a checkbox entre 27 territórios / 435 municípios; a regra sai como **helper puro em `src/lib/`** (`orderFilterOptionsSelectedFirst`), consumido pelo Popover desktop e pelo select mobile e pronto para **B21**/E12 — sem extrair o head genérico (gatilho do B21 segue sendo o 3º call site; **B22** promove o tooltip pelo mesmo critério de "peça compartilhada sem head genérico") · ~0,25–0,5d · Impeccable B · soft: B16 ✓ / B21 / B22 · [plano](plans/selecionados-no-topo-filtros-header.md)
- **Cenário junto aos filtros** em `/campanha/municipios` — mover `VoteEstimateScenarioField` para a fileira/barra slim de `MunicipalityFilters` (com **B16**, a barra deixa de carregar os selects de coluna); overview só consome o contexto · ~0,25–0,5d · Impeccable B · soft: B16 · [plano](plans/cenario-junto-filtros-municipios.md)
- ~~**B16+** escala/DRY pós-B16~~ — **absorvido pelo Pass 2 W1-D1 (2026-07-25)**: `useOptimistic`, facet por slugs, hrefs via serializador canônico, facets no mesmo `Promise.all` · [plano](plans/escala-dry-pos-b16.md)
- **O0+** escala/DRY pós-Onda 0 · [plano](plans/escala-dry-pos-onda0.md)
@@ -109,7 +130,7 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re
- **C5** GOTV — validar com produto antes de planejar (hoje só design-ref; depende de C2 dados reais).
- **O8** "ilhas isoladas" — fluxo de informação entre política/comunicação/território (pedido de ajuda direto na sessão 2026-07-23; adjacente ao produto) · discovery antes de qualquer build; candidato natural se virar build: D2 sino/resumo semanal.
-- **O3** jargão — resgatar o Bloco B (uso observado) das notas dos entrevistadores e/ou 2ª sessão observada antes de investir em glossário além do R6 ([IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md)).
+- **O3** jargão — resgatar o Bloco B (uso observado) das notas dos entrevistadores e/ou 2ª sessão observada antes de investir em glossário além do R6; parcialmente endereçado por evidência datada: **E18 ✓** (página de conceitos, pedido de 2026-07-24) e **B22** (explicação por coluna no header, pedido de 2026-07-25) — o que continua sem evidência é glossário em badges/estimativas (FD2) ([IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md)).
### Grafo de dependências (abertos + predecessores mínimos)
@@ -132,6 +153,24 @@ flowchart TD
B17n["B17 Seletor de colunas da lista"]
B18n["B18 Filtros salvos + atalho sidebar"]
B19n["B19 ✓ Gerenciar assessores"]
+ B20n["B20 Municípios prioritários no Início"]
+ B21n["B21 Página dos Territórios de Identidade"]
+ B22n["B22 Explicação por coluna no header das listas"]
+ B23n["B23 Tooltip nas células das listas"]
+ B24n["B24 Auto-save + justificativa no popover de Tendência"]
+ B25n["B25 Link do território na lista de municípios"]
+ B26n["B26 Registrar sinal na célula Último sinal"]
+ B27n["B27 Combobox + chips no popover de Assessores"]
+ B28n["B28 E-mail + celular na lista de lideranças"]
+ B30n["B30 Convite WhatsApp na lista de lideranças"]
+ B31n["B31 Coluna de dobradinhas na lista de lideranças"]
+ B32n["B32 Editar Status de apoio na célula da lista"]
+ B33n["B33 Sort + filtro no header da lista de dobradinhas"]
+ B34n["B34 Chips editáveis de município na lista de lideranças"]
+ B37n["B37 Chips editáveis de município na lista de dobradinhas"]
+ B36n["B36 Chips de lideranças na lista de dobradinhas"]
+ B29n["B29 Sort + filtro no header da lista de lideranças"]
+ C13n["C13 Plano de Ação → Atividade (rename + guard de vocabulário)"]
E4R["E4R ✓ Import planilha (seed estratégia)"]
A11n["A11 ✓ Posição em votos"]
E17n["E17 ✓ Tabela TI no Início"]
@@ -152,16 +191,87 @@ flowchart TD
end
E4R -.metas seedadas.-> E8i
+ E4R -.priority alta.-> B20n
E8i -.conceitos a documentar.-> E18n
+ E8i -.cobertura.-> B20n
+ E9i -.critério déficit.-> B20n
+ E9i -.célula de frescor.-> B26n
+ C12i -.sinal tipado.-> B26n
B15n -.contrato sort/dir.-> A11n
B15n -.header sort.-> B16n
B15n -.ids de coluna.-> B17n
B16n -.barra slim.-> B17n
B16n -.barra slim.-> B18n
B17n -.vizinho barra.-> B18n
+ B16n -.densidade do th.-> B22n
+ E18n -.oneLiner dos conceitos.-> B22n
+ B22n -.colunas se explicam.-> B17n
+ B22n -.header compartilhado.-> B21n
+ B22n -.primitivo compartilhado.-> B23n
+ B19n -.carteira de assessores.-> B23n
+ E4R -.notas de tendência.-> B23n
+ E4R -.notas a corrigir.-> B24n
+ B23n -.nota no Popover vira campo.-> B24n
+ B23n -.células se explicam.-> B21n
A11n -.coluna/ordenação.-> E9i
B16n -.pattern header.-> E9i
B17n -.viewport colunas.-> E9i
+ E17n -.rollup + tabela.-> B21n
+ B15n -.header sort.-> B21n
+ B16n -.header filtro.-> B21n
+ B17n -.colunas como dado.-> B21n
+ B21n --> B25n
+ B17n -.coluna ocultável.-> B25n
+ B24n -.contrato do popover na linha.-> B26n
+ B23n -.tap na mesma célula.-> B26n
+ B19n -.delta + lock + chips.-> B27n
+ B24n -.endpoint JSON sem revalidar.-> B27n
+ B23n -.tooltip no mesmo gatilho.-> B27n
+ B16n -.busca em Popover.-> B27n
+ B26n -.contratos vizinhos na mesma linha.-> B27n
+ E9i -.badge sem responsável.-> B27n
+ E4R -.contas de assessor.-> B27n
+ B19n -.copy + WhatsApp na lista.-> B28n
+ B28n -.ícone na mesma linha.-> B30n
+ B19n -.chips + busca + delta.-> B31n
+ B27n -.mesma peça de célula.-> B31n
+ B29n -.colunas da mesma tabela.-> B31n
+ B28n -.mesma tabela.-> B31n
+ B30n -.mesma tabela.-> B31n
+ B17n -.coluna ocultável.-> B31n
+ E4R -.dobradinha só no município.-> B31n
+ B31n -.rede vinculada por dobradinha.-> E13i
+ B24n -.mesma família de decisão.-> B32n
+ B29n -.gatilho fechado.-> B32n
+ B31n -.mesma tabela, célula vizinha.-> B32n
+ B29n -.mesma corrida de extração.-> B33n
+ B21n -.mesma corrida de extração.-> B33n
+ B15n -.contrato a espelhar.-> B33n
+ B16n -.contrato a espelhar.-> B33n
+ B19n -.lib de origem.-> B34n
+ B27n -.mesma peça de célula.-> B34n
+ B31n -.mesma tabela + decisão de container.-> B34n
+ B28n -.mesma tabela.-> B34n
+ B30n -.mesma tabela.-> B34n
+ B32n -.mesma tabela.-> B34n
+ B17n -.tabela cresce.-> B34n
+ E16i -.painel do município a revalidar.-> B34n
+ B34n -.mesma promoção de lib + container.-> B37n
+ B31n -.mesma família de pedido.-> B37n
+ B19n -.lib de origem + cálculo de próximo array.-> B37n
+ B33n -.mesma tabela.-> B37n
+ E4R -.dobradinha só no município.-> B37n
+ B31n -.mutação/schema/lock reusados sem alteração.-> B36n
+ B19n -.padrão de origem.-> B36n
+ B33n -.mesma tabela.-> B36n
+ E4R -.fichas vazias dos dois lados.-> B36n
+ B15n -.contrato sort/dir.-> B29n
+ B16n -.popover de filtro.-> B29n
+ B21n -.3º call site do head.-> B29n
+ B28n -.colunas vizinhas na mesma tabela.-> B29n
+ B29n -.coluna e filtro de nível.-> E14i
+ E4R -.fichas seedadas na lista.-> B29n
+ B21n -.superfície regional.-> E12i
E17n -.primeira fatia.-> E12i
E8i --> E9i --> E11i
C12i --> E11i
@@ -178,16 +288,17 @@ flowchart TD
C12i -.sinais.-> E16i
E16i -.dossiê pré-visita.-> E13i
A6 -.dobradinha.-> E13i
+ C13n -.vocabulário e caminhos da vertical.-> E13i
C2prod -.dados reais.-> E11i
E11i -.decisões acumuladas.-> E15i
B8F2 -.ZE Salvador.-> B14n
```
-Paralelizáveis agora: **E10**/**E12** (E8 ✓ desbloqueou ambos), **E11** (E9 ✓ entregue; ainda espera E10), **B14** (atalho geo no Início; soft B8 F2 só para ZE Salvador), **B17** (seletor de colunas; soft B15 ✓/B16 ✓ — pousa no header de coluna atual), **B18** (filtros salvos + submenu Municípios; soft B16 ✓/B17 — botão na barra), fill-ins (ícone de prioridade na lista, Cenário junto aos filtros — encaixa na barra slim de B16 ✓ —, O0+, RS+). **D3** só após smoke + folga e só se o fluxo sede-digita (C12 ✓) não absorver os deltas do ZAP (não compete com E9).
+Paralelizáveis agora: **E10**/**E12** (E8 ✓ desbloqueou ambos), **E11** (E9 ✓ entregue; ainda espera E10), **B14** (atalho geo no Início; soft B8 F2 só para ZE Salvador), **B20** (atalho prioritários no Início; soft E4R/E8/E9 ✓ — todos entregues), **B21** (página dos TIs; sem dep dura — E17 ✓/B15 ✓/B16 ✓ já entregues; E12 pousa nela depois; **B25** é o único item que espera B21 — o link do território na lista só existe depois do destino), **B17** (seletor de colunas; soft B15 ✓/B16 ✓ — pousa no header de coluna atual), **B18** (filtros salvos + submenu Municípios; soft B16 ✓/B17 — botão na barra), **B22** (explicação por coluna no header; sem dep dura — B15 ✓/B16 ✓/E18 ✓), **B23** (tooltip nas células; sem dep dura — soft B22 só pelo primitivo compartilhado, quem chegar primeiro promove), **B24** (auto-save + justificativa no popover de Tendência; sem dep dura — soft B23 só pela ordem em que a nota entra no Popover), **B26** (registrar sinal na célula "Último sinal"; sem dep dura — E9 ✓/C12 ✓ já entregues; soft B24/B23 só pela coerência de contrato na mesma linha), **B27** (combobox + chips + auto-save no popover de Assessores; sem dep dura — B19 ✓ já deu o delta+lock e o padrão de chips; soft B24 pelo endpoint e B23 pelo gatilho), **B28** (e-mail + celular na lista de lideranças; sem dep dura — B19 ✓ é só o padrão soft de copy/WhatsApp; `phone` já está no view model), **B29** (sort + filtro no header da lista de lideranças; sem dep dura — B15 ✓/B16 ✓ deram o contrato e o popover; soft B21 e B28 só pela ordem de chegada: quem entrar depois herda o head compartilhado / encaixa as colunas do outro), fill-ins (renomear coluna Cobertura → quick win de densidade no header; remover coluna Tipo — quick win de densidade; ícone de prioridade na lista; Cenário junto aos filtros — encaixa na barra slim de B16 ✓ —; selecionados no topo nos filtros do header — helper puro que B21 reusa —; O0+, RS+). **B30** (convite WhatsApp na lista de lideranças; sem dep dura — reusa integralmente a action/collection existentes; soft B28 só pelo ícone na mesma linha, qualquer um pode ir primeiro), **B31** (coluna de dobradinhas na lista de lideranças; sem dep dura — o dado já está no view model e o catálogo já tem loader; soft B27 pela peça de célula, que só vira extração compartilhada no 3º call site, e B28/B29/B30 pela ordem de chegada na mesma tabela), **B32** (editar Status de apoio na célula da lista de lideranças; sem dep dura — o campo já existe e já é opcional no schema de update; soft B24 só pela ordem de extração do guard CSRF compartilhado, e B29/B31 só pela convenção "sem modo Editar" e a mesma tabela), **B33** (sort + filtro no header da lista de dobradinhas; sem dep dura — B15 ✓/B16 ✓ deram o contrato; soft B29/B21 só pela ordem de chegada na mesma corrida de extração do chrome compartilhado, quem entrar primeiro paga a peça e o outro só consome), **B34** (chips editáveis de municípios na lista de lideranças; sem dep dura — B19 ✓ já deu o delta+lock, o padrão de chips e o loader do catálogo a promover; soft B27/B31 pela mesma peça de célula editável — quem chegar depois herda o combobox/Popover em vez de reinventar —, e B28/B30/B32 só pela mesma tabela; _decisão a validar com produto_: Popover por célula em vez do toggle "Editar" pedido literalmente, mesmo precedente do B31/B32), **B37** (chips editáveis de municípios na lista de dobradinhas; sem dep dura — B19 ✓/B34 já dão a lib a promover e o desenho do Popover+combobox; soft B34 só pela ordem de chegada em quem faz a promoção da lib, B31 pela mesma família de pedido em outra tabela, B33 pela mesma tabela; _mesma decisão a validar com produto_ do B31/B32/B34: Popover por célula, não toggle). **B36** (coluna de lideranças na lista de dobradinhas; sem dep dura — reusa integralmente a mutação/schema/lock que o B31 desenha para a mesma relação, do lado oposto; soft B31 pela corrida de extração da célula/action compartilhadas, B19 ✓ pelo padrão de origem, B33 pela mesma tabela), **C13** (renomear Plano de Ação → Atividade; sem dep dura — não toca nenhuma das listas em curso (B26–B37 são municípios/lideranças/dobradinhas), então roda em paralelo a elas; a única ordem que importa é **antes de E13**, que constrói dentro da vertical renomeada, e antes do congelamento ~20/09 por carregar migration; PR mecânico e isolado, não misturar com feature). **D3** só após smoke + folga e só se o fluxo sede-digita (C12 ✓) não absorver os deltas do ZAP (não compete com E9).
### Sequência por janela (só pendentes)
-**Janela 1 — agora → 05/08 (convenções):** smoke pós-deploy em produção + onboarding do time (Onda 0 §2/§4); **E4R ✓** em código (aplicar `pnpm db:seed:projecao` em produção após smoke); **E8 ✓** conta da cadeira entregue; **E17 ✓** tabela TI no Início; **B15 ✓** (sort por coluna) e **A11 ✓** (posição em votos + `sort=votos`; o default do staff passou a `deficit` no E9 ✓) entregues 2026-07-24; **E18 ✓** documentação de conceitos (`/campanha/conceitos`, v1 só E8) entregue 2026-07-24; **B19 ✓** gerenciar assessores (caminho do onboarding sem `/admin` — criar/ativar contas e carteiras) entregue 2026-07-24; **B16 ✓** filtros no header entregue 2026-07-25; **B17** (seletor de colunas) / **B18** (filtros salvos + atalho sidebar) se folga de UX da lista no onboarding; gate de adoção: sinal Little Hire — ≥1 update espontâneo até 30/07, cobrança da tabela dispensada (planilhas já em `docs/sheets/`) — acompanhamento em [IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md); **R6** critique/polish; **E9 ✓** entregue no mesmo dia; **B14** (atalho geo) se sobrar folga de campo no onboarding; Onda 0 jurídica em paralelo (externa).
+**Janela 1 — agora → 05/08 (convenções):** smoke pós-deploy em produção + onboarding do time (Onda 0 §2/§4); **E4R ✓** em código (aplicar `pnpm db:seed:projecao` em produção após smoke); **E8 ✓** conta da cadeira entregue; **E17 ✓** tabela TI no Início; **B15 ✓** (sort por coluna) e **A11 ✓** (posição em votos + `sort=votos`; o default do staff passou a `deficit` no E9 ✓) entregues 2026-07-24; **E18 ✓** documentação de conceitos (`/campanha/conceitos`, v1 só E8) entregue 2026-07-24; **B19 ✓** gerenciar assessores (caminho do onboarding sem `/admin` — criar/ativar contas e carteiras) entregue 2026-07-24; **B16 ✓** filtros no header entregue 2026-07-25; **B20** Municípios prioritários no Início (atalho ranqueado + markup; quick win de onboarding — a seção hoje mente por omissão); **B21** (página dos Territórios de Identidade — a leitura regional sai do painel do Início e ganha tela própria com sort/filtro; útil já no onboarding para dividir carteiras) se folga — e, logo atrás dela, **B25** (link do território na célula da lista: quarto de dia, é o que faz a página nova ser encontrada de dentro do trabalho); **B22** (explicação por coluna no header — a tabela deixa de exigir o autor do E8/E9 ao lado para ser lida; quick win de onboarding, meio dia); **B23** (tooltip nas células — nomes dos assessores no hover da coluna, hoje só iniciais, e a justificativa da tendência, hoje invisível na lista; capacidade genérica da célula, meio dia, casa com B22 pelo mesmo primitivo); **B24** (auto-save + justificativa no popover de Tendência — a mesa corrige em massa as notas semeadas pelo E4R ✓ sem abrir 435 telas de edição; meio dia, casa com B23 pela mesma célula); **B26** (registrar sinal na própria célula "Último sinal" — fecha o laço do E9 ✓: hoje a fila diz onde está frio e obriga a sair dela para registrar qualquer coisa; ~0,75 dia, e é o item que mais aumenta a chance do gate de adoção abaixo, porque o registro passa a caber no gesto de varrer a lista, inclusive no celular); **B27** (combobox + chips + auto-save nos Assessores — é literalmente a tela em que o CG divide as carteiras no onboarding: hoje procura o nome numa coluna de checkboxes e confirma num botão; ~0,75 dia, casa com B24 pelo mesmo endpoint e com B23 pelo mesmo gatilho); **B28** (e-mail + celular na lista de lideranças — copy + WhatsApp como em assessores, sem abrir a ficha; ~0,5–0,75 dia, quick win de campo no onboarding da rede); **B30** (convidar para completar cadastro por WhatsApp na mesma lista — reusa o convite que já existe no detalhe, sem abrir a ficha; ~0,25–0,5 dia, o quick win mais barato desta leva porque não toca action/collection nenhuma; casa com B28 pela mesma linha, ícone distinto); **B31** (coluna de dobradinhas na mesma lista — é o preenchimento em lote que o onboarding exige: o E4R ✓ deixou dobradinha só no município e as fichas vazias, e completar uma a uma pelo detalhe é o gesto que trava a mesa; ~1 dia, casa com B27 pela mesma peça de célula — quem for primeiro define o padrão do chip+combobox in-list); **B32** (editar Status de apoio na célula da mesma lista — pedido de produto explícito para não repetir o toggle "Editar" do B19 ✓; fecha o adiado do próprio B29; ~0,4–0,5 dia, o quick win mais barato entre os popovers de célula desta leva, casa com B24 pelo guard CSRF compartilhado); **B34** (chips editáveis de municípios na mesma lista — a coluna "Municípios" ganha o mesmo tratamento da coluna homônima em Assessores (B19 ✓): chips com link + Popover de edição com combobox, em vez do texto morto atual; ~1–1,25 dia, casa com B27/B31 pela mesma peça de célula editável — quem entrar antes define o padrão do combobox+chip in-list que os outros herdam); **B37** (o mesmo tratamento de chips editáveis na coluna "Municípios" de `/campanha/dobradinhas` — hoje um número morto, `municipalityCount`; ~0,75–1 dia, herda a lib promovida pelo B34 se ele já tiver landado, senão promove ela mesma); **B36** (a coluna "Lideranças" da mesma lista ganha o mesmo tratamento — chips nomeados + Popover + busca + auto-save por delta, reusando sem alteração a mutação que o B31 desenha para o lado oposto da mesma relação; ~0,5–1,5 dia dependendo de quem entre B31/B36 chega primeiro na corrida de extração da célula compartilhada); **B33** (sort + filtro no header de `/campanha/dobradinhas` — pedido explícito de reuso: Nome/Partido no header e filtro por Partido, sem duplicar o chrome de `/campanha/municipios`; ~0,5–1,5 dia dependendo de quem chega primeiro na corrida de extração do chrome compartilhado com B29/B21; entra perto de B29 por reusar a mesma peça); **B29** (sort + filtro no header da mesma lista — é a tela onde o assessor recebe a carteira e precisa recortar "quem ainda está a abordar aqui" / "quem ainda não tem acesso ao app" antes de convidar; ~1,5–2 dias porque paga também a extração do head compartilhado, então entra depois dos quick wins — mas antes se B21 for adiado, para o head nascer compartilhado); **B17** (seletor de colunas) / **B18** (filtros salvos + atalho sidebar) se folga de UX da lista no onboarding; fill-in **renomear coluna Cobertura** (header curto na fila — quarto de dia; glossário/KPI intactos); fill-in **remover coluna Tipo** (densidade morta na fila — quarto/meio dia; Salvador via slugs cobre ZE); fill-in **selecionados no topo nos filtros do header** (desmarcar sem caçar checkbox; helper puro que B21 herda — quarto de dia); **C13** (renomear "Plano de Ação" → "Atividade" em toda a vertical — o time que entra agora aprende **uma** palavra, a que já usa em campo, em vez de decorar uma tradução; ~1–1,5 dia, PR mecânico e isolado, e é o único item desta janela que carrega migration, então quanto mais cedo melhor: cada item novo na vertical e cada semana de uso aumentam o custo, e depois do congelamento ~20/09 ele deixa de ser possível); gate de adoção: sinal Little Hire — ≥1 update espontâneo até 30/07, cobrança da tabela dispensada (planilhas já em `docs/sheets/`) — acompanhamento em [IMPROVE-APP-PLAN.md](IMPROVE-APP-PLAN.md); **R6** critique/polish; **E9 ✓** entregue no mesmo dia; **B14** (atalho geo) se sobrar folga de campo no onboarding; Onda 0 jurídica em paralelo (externa).
**Janela 2 — 05/08 → 16/08 (pré-propaganda):** C2 dados reais assim que o jurídico liberar; ~~**E9** fila de alocação~~ (entregue 2026-07-24, antecipada da janela 2); ~~**E16** dossiê do município~~ (entregue 2026-07-25, antecipado da janela 2); D2 se sobrar folga.
@@ -199,7 +310,7 @@ Paralelizáveis agora: **E10**/**E12** (E8 ✓ desbloqueou ambos), **E11** (E9
**Não cortáveis:** Onda 0 (jurídico/Consent); ~~**E4R** seed da planilha~~ (entregue 2026-07-24 — ainda aplicar em produção após smoke); ~~**E8** conta da cadeira~~ (entregue 2026-07-24); ~~**C12** registro-fundação~~ (entregue 2026-07-24); C2 dados reais; assimetria declarado×estimado (relação de campo); ~~**E9** fila de alocação~~ (entregue 2026-07-24 — era o mínimo de "inteligência, não planilha"); ~~**B19** gerenciar assessores~~ (entregue 2026-07-24 — CG/candidato não entram em `/admin`).
-**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E12** camada TI (rollup manual por lista; **E17** já dá a leitura regional básica no Início); **E13** planejador de giros (rebaixado na fila de corte em 2026-07-24: "perna"/agenda é a restrição dominante nomeada em campo — cortar só depois de E12; agenda segue manual com J-A/J-B como guia); **E15** backtest (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter a fila E9 ✓ sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** níveis (manter `priority` alta/normal); **B8 F2** polígonos (mapa continua agregado no município; manter F1 bairros); **D2** push (adiar); **A6**; **B14** município mais próximo (lista/busca e Recentes continuam); **B18** filtros salvos (lista + filtros manuais continuam; Visitados cobrem “voltar”); **B17** seletor de colunas (tabela completa continua; scroll horizontal); ~~**B16** filtros no header~~ (entregue 2026-07-25); ~~**B15** sort por coluna~~ (entregue 2026-07-24); ~~**A11**/**E17**~~ (entregues 2026-07-24); ~~**E18** documentação de conceitos~~ (entregue 2026-07-24); ~~**E16** dossiê~~ (entregue 2026-07-25); débitos/fill-ins.
+**Cortes seguros** (se o prazo apertar, nesta ordem): **D5** inbox→rascunhos (manter registro manual + `wa.me`); **D4** envio bridge (manter `wa.me`); **D3** fundação do canal (atalho `wa.me` continua); **E12** camada TI (rollup manual por lista; **E17** já dá a leitura regional básica no Início); **E13** planejador de giros (rebaixado na fila de corte em 2026-07-24: "perna"/agenda é a restrição dominante nomeada em campo — cortar só depois de E12; agenda segue manual com J-A/J-B como guia); **E15** backtest (pós-eleição por definição — cortar = perder o aprendizado 2030); **E11** motor v1 (manter a fila E9 ✓ sem sugestões); **B13** símbolo proporcional (manter quantis/LQ como escala); **E14** níveis (manter `priority` alta/normal); **B8 F2** polígonos (mapa continua agregado no município; manter F1 bairros); **D2** push (adiar); **A6**; **B14** município mais próximo (lista/busca e Recentes continuam); **B18** filtros salvos (lista + filtros manuais continuam; Visitados cobrem “voltar”); **B21** página dos Territórios de Identidade (o painel do E17 ✓ no Início continua entregando a leitura regional) — **B25** cai junto por definição (sem destino, sem link); **B17** seletor de colunas (tabela completa continua; scroll horizontal); **B22** explicação no header (a página `/campanha/conceitos` do E18 ✓ continua explicando as métricas do E8 — cortar só custa o atalho em contexto, e o onboarding presencial cobre no curto prazo); **B23** tooltip nas células (as iniciais + o Popover de edição do coordenador continuam dando os nomes, e a justificativa da tendência continua no detalhe do município — cortar custa a leitura a olho na varredura; corte junto com B22, já que dividem o primitivo); **B24** auto-save + justificativa na Tendência (o botão "Salvar" continua funcionando e a justificativa continua editável em `/[slug]/editar` — cortar custa coerência com a célula vizinha e um desvio conhecido do princípio 4; é o corte mais barato desta lista, mas também o mais barato de fazer); **B27** combobox + chips nos Assessores (a lista de checkboxes com "Salvar" continua atribuindo — cortar custa a mesma coerência do B24 mais o tempo do CG procurando nomes numa coluna durante o onboarding; corte junto com B24, já que dividem o endpoint); **B28** e-mail + celular na lista de lideranças (a ficha `/liderancas/[id]` continua mostrando o contato — cortar custa o gesto de copiar/abrir ZAP na varredura; quick win barato de cortar e de fazer); **B30** convite de completar cadastro por WhatsApp na lista (o botão do detalhe continua gerando o convite; cortar custa só o atalho sem abrir a ficha — é o corte mais barato desta faixa, porque não toca nenhuma action/collection); **B31** coluna de dobradinhas nas lideranças (o `RelationMultiSelect` da ficha interna continua vinculando — cortar custa o preenchimento em lote do onboarding, uma ficha aberta por liderança, e mantém o vínculo invisível na varredura; corte junto com B27, já que dividem a peça de célula, e antes de B29 só se a mesa já tiver preenchido as dobradinhas à mão); **B32** editar Status de apoio na célula (a ficha `/liderancas/[id]` continua trocando o status pelo `NativeSelect` do formulário — cortar custa só o atalho de um clique a mais por linha; é o corte mais barato desta faixa, junto com B30, porque não toca nenhum contrato de dado); **B34** chips editáveis de municípios na lista de lideranças (o texto simples `row.municipalityNames.join(', ')` continua legível e a ficha `/liderancas/[id]` continua vinculando município via formulário — cortar custa o link direto ao município, a navegação por chip e o atalho de adicionar/remover sem abrir a ficha; corte junto com B31, já que dividem a mesma peça de célula editável, e antes de B29 pelo mesmo motivo de coerência); **B37** chips editáveis de municípios na lista de dobradinhas (o número `municipalityCount` continua legível e a ficha de município continua vinculando dobradinha pelo `MunicipalityStrategyForm` — cortar custa o link direto, a navegação por chip e o atalho de adicionar/remover sem abrir a ficha do município; corte junto com B34, já que dividem a mesma lib e a mesma decisão de container); **B36** coluna de lideranças na lista de dobradinhas (a contagem `leadershipCount` continua legível e a ficha `/liderancas/[id]` continua vinculando dobradinha via formulário — cortar custa o link direto à liderança, a navegação por chip e o atalho de adicionar/remover sem abrir a ficha; corte junto com B31, já que dividem a mesma mutação/schema/lock); **B33** sort + filtro no header das dobradinhas (a busca por nome e a ordem fixa por nome continuam funcionando — lista pequena hoje, cortar custa só o recorte por partido e a ordenação por partido; corta antes de B29 se o chrome compartilhado ainda não tiver sido pago por nenhum dos dois); **B29** sort + filtro no header das lideranças (a busca por nome continua funcionando e o escopo do assessor já reduz a lista — cortar custa o recorte por status/setor/município no onboarding da rede e mantém o header rico como cópia única de municípios; por ser o item mais caro desta faixa (~1,5–2 dias com a extração do head), cai **antes** do B26, que é o que sustenta o gate de adoção); **B26** registrar sinal na célula (o formulário completo em `/[slug]?tab=updates` continua sendo o caminho — cortar custa o laço da fila do E9 ✓ e o registro em campo pelo celular, que é justamente onde o gate de adoção se decide; por isso cai **depois** de B22/B23/B24); **C13** renomear Plano de Ação → Atividade (a vertical continua funcionando com o nome antigo — cortar custa o atrito de tradução em cada conversa de onboarding e deixa o código, o banco e a URL divergindo do vocabulário do time; **cortar cedo ou nunca**: é o único item cortável desta faixa cujo custo cresce com o tempo — cada item novo na vertical multiplica as ocorrências, o uso real enche a tabela e o congelamento ~20/09 fecha a porta da migration, então cortar aqui significa cortar para o ciclo inteiro); **B20** prioritários no Início (lista `?priority=alta` continua; só perde o atalho — mas a seção atual mente, então cortar = conviver com o bug); fill-in **renomear coluna Cobertura** (a lista continua legível com o rótulo longo — corte = header mais largo); fill-in **remover coluna Tipo** (a lista continua completa; só mantém densidade morta — corte = conviver com a coluna); ~~**B16** filtros no header~~ (entregue 2026-07-25); ~~**B15** sort por coluna~~ (entregue 2026-07-24); ~~**A11**/**E17**~~ (entregues 2026-07-24); ~~**E18** documentação de conceitos~~ (entregue 2026-07-24); ~~**E16** dossiê~~ (entregue 2026-07-25); demais débitos/fill-ins.
## Bloqueadores atuais