Nova rota staff-only /campanha/conceitos (glossário curado em src/lib/campaignIntelligenceConcepts.ts, revisado junto das fórmulas que descreve em municipalityPotential.ts/goalCoverage.ts) com descoberta por "Saiba mais" nos tooltips do card "Conta da cadeira" e entrada fixa no rodapé da sidebar (staffSecondaryNav). Corrige defeito E8 de formatElectionNumber (meta fracionária lida como cem mil em pt-BR). Ajustes pós-/simplify: MetricExplanation.conceptID obrigatório, href da sidebar desacoplado do módulo de conteúdo (client bundle).
15 KiB
Documentação de conceitos de inteligência de campanha
Status: entregue (2026-07-24)
Atualizado em: 2026-07-24
Item do roadmap: docs/roadmap.md ("Demais itens abertos", E18 — adjacente ao programa Inteligência de campanha E8–E16/B13/C12)
Impeccable: C — rota nova /campanha/conceitos, sem design-ref
Appetite: ~0,5–1 dia eng (página estática + módulo de conteúdo + 4 links "saiba mais" no card existente)
Responsável: —
Design (Impeccable)
Âncoras: PRODUCT.md (register product) · DESIGN.md (tema data-theme='campaign').
Na implementação (implement-roadmap-item): shape compacto (já feito por este brief) → craft → critique → polish. harden/optimize só se o Passo 8 do implement-roadmap-item acionar gatilho.
Brief compacto:
- Persona / contexto: Alex (CG) ou assessor lendo um card de inteligência (ex.: "Conta da cadeira") e não lembrando o que "teto do campo", "captura" ou "roll-off" significam — quer a explicação sem sair do fluxo, e sem precisar perguntar no grupo.
- Job principal: explicar, em prosa e fórmula, o que cada número de inteligência do produto mede e como é calculado — não é um FAQ geral do app.
- Estratégia de cor: Restrained (default) — página de leitura, sem hero/gauge.
- Edit where you see: não se aplica (conteúdo é read-only, curado por PR — ver Decisões travadas).
- Anti-goals: wiki geral do
/campanha(onboarding, "como criar um plano"); FAQ de produto; fórmula renderizada matematicamente (KaTeX) para um público que não é acadêmico; segunda navegação paralela na sidebar.
Dados → decisão → apresentação
- Vou apresentar dados? Não (N/A). A página só explica em prosa fórmulas já expostas em E8 (e, com o tempo, E9–E17); no máximo um exemplo numérico ilustrativo estático (ex.: o par hipotético "Feira de Santana: meta 3.000 / comprometido 1.200" já usado nesta sessão), nunca um cálculo ao vivo sobre um município real.
- Decisões desbloqueadas: nenhuma — este item explica decisões que outras superfícies (E8 hoje; E9–E17 depois) já habilitam. Não é vaidade porque resolve confusão de vocabulário real e evidenciada nesta própria sessão, mas não é ele quem decide alocação.
- Forma escolhida: prosa + fórmula em texto monospace, seção por conceito (degrau mais pobre da escada). Rejeitado: exemplo vivo computado de município real (2ª superfície de dados dentro da doc, foco de manutenção — ver Rabbit holes); fórmula em KaTeX/notação matemática (lib nova para um único consumidor, público não é acadêmico).
- Anti-goals de dado: N/A (sem dado).
Contexto
O card "Conta da cadeira" no detalhe do município (/campanha/municipios/<slug>, MunicipalityGoalAccountCard.tsx) ganhou nesta mesma sessão tooltips de hover (GoalAccountMetric) explicando os 4 números de diagnóstico — teto do campo projetado, captura 2022, share intracampo e roll-off — depois que o usuário relatou não entender a que cada número se referia. Depois de ler as explicações, o próprio usuário reconheceu: "são análises muito interessantes […] mas são conceitos complicados […] importância de criarmos uma página de documentação para explicar cada conceito de inteligência usado na vertical /campanha e como estes valores são calculados." Isso é evidência de campo concreta e datada (2026-07-24) do mesmo tipo que a questão em aberto O3 ("jargão") do plano-mestre inteligencia-campanha.md e do roadmap pedia antes de investir além do R6 — mas o produto ainda não tem lugar para acumular essas explicações fora do próprio tooltip pontual, e o programa de inteligência (E9–E17) vai adicionar mais conceitos (LQ, quantis, níveis N0–N4, padrões de sugestão P1–P8, elegibilidade de giro) que vão precisar da mesma explicação.
Objetivos
- Página staff-only
/campanha/conceitosdocumentando, em v1, os conceitos já entregues em E8: teto do campo projetado, captura, share intracampo, roll-off, meta/meta sugerida e cobertura da meta — a mesma lista já coberta pelos tooltips deMunicipalityGoalAccountCard.tsxe peloCampaignInfoHintdo card. - Cada conceito documentado com: nome, uma frase do que mede, fórmula em texto, "por que isso importa" (ligação com a decisão que ele habilita — ex.: captura é só diagnóstico, não entra na meta).
- Links contextuais "saiba mais" a partir dos tooltips/
CampaignInfoHintexistentes que já mencionam esses conceitos, apontando para a âncora certa da página (/campanha/conceitos#<id>) — sem duplicar o texto do tooltip, só estendê-lo. - Sem migration, sem collection, sem Consent, sem server action.
Decisões travadas
- Conteúdo em módulo de código estático (
src/lib), não em collection Payload. As explicações são 1:1 com fórmulas vivas emmunicipalityPotential.ts/goalCoverage.ts(e, depois, nos utilities de E9–E17); um texto curado por PR ao lado do código que ele descreve fica revisável junto da mudança de fórmula. Rejeitado: collection admin (richText) — deixaria a cópia dessincronizar da fórmula real sem qualquer teste/lint pegando, exigiria grupoadmin.groupe access novos para um conteúdo que nenhum coordenador vai editar no/admin. - Rota nova staff-only (
isCampaignStaff), sem acesso deleader. Todo conceito documentado descreve campos staff-only (estimativas, metas, teto do campo) que a liderança nunca vê — expor o glossário a ela vazaria vocabulário sem contexto (M4 vocabulário duplo, mesmo princípio de E14). Rejeitado: liberar leitura a todocampaignUser— nada para a liderança ancorar a leitura. - Descoberta por link contextual nos tooltips + entrada no rodapé da sidebar. Os tooltips levam à âncora do número que a pessoa está olhando; a sidebar garante um caminho de volta sem depender de lembrar em qual card estava o link. A entrada não entra na
staffNav(os 7 destinos de trabalho,nav.ts) nem na barra inferior do mobile: vai num grupo próprio colado no rodapé (staffSecondaryNav), material de referência separado de onde a mesa trabalha — Design Principle "Clarity under pressure". Rejeitado: oitavo item no meio dastaffNav, competindo com Municípios/Lideranças/Demandas. - i18n e naming: identificadores em inglês (
campaignIntelligenceConcepts.ts,ConceptSection,conceptId), strings visíveis em pt-BR; rota/campanha/conceitos(Português, mesmo padrão de/campanha/dobradinhas//campanha/planos— não é param dinâmico).
Questões em aberto
- Escopo de v1: só E8 ou esperar o programa fechar? Opções: A) documentar só os conceitos já entregues (E8) | B) esperar E9–E17 fecharem para lançar uma página completa. Recomendação: A — a confusão evidenciada agora é sobre E8; cada item subsequente do programa (E9, E10, B13, E11, E12, E13, E14) ganha sua própria seção como parte do checklist de verificação desse item (
implement-roadmap-itemPasso de verificação), evitando um E18-v2 que espera 6 outros itens não construídos. - Onde mora o conteúdo (TSX puro vs MDX)? Opções: A) array TS de "conceito" renderizado por um componente | B) arquivos MDX. Recomendação: A — reaproveita
Card/tipografia shadcn já no produto; MDX adiciona toolchain nova para um único consumidor. - Fórmulas com notação matemática (KaTeX)? Opções: A) texto monospace/pseudo-notação (
captura = pledges ÷ tetoDoCampo) | B) lib de renderização matemática. Recomendação: A — nenhum outro lugar do produto usa notação matemática; público é staff de campo, não acadêmico.
Abordagem proposta
flowchart LR
Content["campaignIntelligenceConcepts.ts<br/>(array de conceitos, src/lib)"]
Page["/campanha/conceitos<br/>page.tsx (staff-only)"]
Card["MunicipalityGoalAccountCard.tsx<br/>GoalAccountMetric + CampaignInfoHint"]
Content --> Page
Card -.link saiba mais.-> Page
Componentes:
src/lib/campaignIntelligenceConcepts.ts(novo, client-safe, dado puro): array de{ id, category, title, oneLiner, formula, whyItMatters }— v1 com os ~6 conceitos de E8 (teto do campo, captura, share intracampo, roll-off, meta/meta sugerida, cobertura da meta). Texto extraído das explicações já escritas emMunicipalityGoalAccountCard.tsxe noCampaignInfoHintdo card, sem reinventar a redação.src/app/(campaign)/campanha/(app)/conceitos/page.tsx(novo, server component): gategetCampaignUser()+isCampaignStaff+redirect('/campanha')paraleader— mesmo padrão de.../planos/page.tsx; renderizaCampaignPageShellcom uma lista deCardpor conceito,id={concept.id}no elemento da seção para permitir#anchor.MunicipalityGoalAccountCard.tsx: cadaGoalAccountMetrice oCampaignInfoHintdo título ganham umLink"Saiba mais" para/campanha/conceitos#<id>ao final do texto explicativo — não substitui o tooltip, só estende.- Sem migration, sem collection, sem server action, sem Consent.
Dependências
- Duro: E8 ✓ (os conceitos a documentar em v1 já existem e estão entregues).
- Suave: cresce conforme E9/E10/B13/E11/E12/E13/E14 entregarem — cada um adiciona sua seção ao array de conceitos e seus próprios links "saiba mais", sem exigir um re-trabalho deste item.
- Nenhuma dependência de site público/admin.
Não escopo
- Documentar conceitos de itens ainda não construídos (E9–E17) — cada item adiciona sua própria seção quando entregar (ver Questões em aberto).
- Glossário inline automático / detecção de jargão em toda a UI — é a hipótese O3 do roadmap, ainda sem evidência de Bloco B suficiente para um mecanismo geral; este item resolve o caso concreto já evidenciado (E8) e fixa o padrão, mas não é um sistema automático.
- Onboarding geral do
/campanha("como criar um plano", "como declarar um pledge") — fora do escopo de "conceitos de inteligência"; se pedido, é outro item. - Fórmulas renderizadas matematicamente (KaTeX/rehype-math) — texto monospace basta (Questões em aberto).
Rabbit holes
- Exemplo "ao vivo" com dado real de um município. Se alguém "só completar" com um cálculo dinâmico usando um município real: isso cria uma 2ª superfície de dados dentro da doc (loader, cache, desatualiza se a fórmula mudar sem o exemplo acompanhar). Mitigação: exemplos estáticos escritos à mão no conteúdo (como o par hipotético Feira de Santana já usado nesta sessão).
- Página virar wiki geral do produto. Se alguém "só adicionar" onboarding/FAQ ao lado dos conceitos: explode escopo e mistura dois jobs. Mitigação: escopo travado em "conceitos de inteligência de campanha" (título da página, seções por ID do programa E8–E17), qualquer outra dúvida vira R6/O3.
Adiado com gatilho
Item fixo naGatilho disparado no mesmo dia (pedido explícito em sessão): entrou como grupo secundário no rodapé da sidebar, não nastaffNav(nav.ts).staffNav— ver Entrega.- Exemplo numérico "ao vivo" de município real. Revisitar se o exemplo estático se mostrar insuficiente em uso real (feedback de campo pedindo números reais).
Entrega (2026-07-24)
Implementado como planejado, com três desvios documentados:
- 7 conceitos, não 6. "Votos válidos projetados" entrou como conceito próprio (categoria "Base do cálculo"): a fórmula do teto projetado e a da meta sugerida citam a projeção de válidos, então descrevê-la de fora deixaria as duas explicações circulares. Categorias (
base/diagnostico/meta) já existem no conteúdo — cada fatia futura do programa acrescenta a sua. - Seções planas, sem
Cardpor conceito. É superfície de leitura:articlepor conceito com hairline entre eles, coluna capada emmax-w-prose, e um índice sticky só emlg(num telefone ele empurraria as sete seções abaixo da dobra para duplicar o que rolar já dá).article:targetrecebe um tintbg-muted/50— é o que orienta quem chegou por âncora no meio da página. Cards empilhados seriam a resposta preguiçosa (DESIGN.md, "Flat-By-Default"). - Dois caminhos de "saiba mais", por acessibilidade. Cada tooltip de métrica (
MetricExplanation, propconceptID) leva à âncora do conceito; o PopoverCampaignInfoHintdo título leva à página inteira. O Popover é o caminho de teclado — conteúdo de Tooltip do Radix não entra na ordem de tabulação. Para o toque funcionar,MunicipalityHoverTooltippassou a tratar o próprio conteúdo como "dentro" no listener depointerdown(antes o link era desmontado antes do clique chegar).
Ainda na mesma sessão, a pedido do usuário, a página ganhou entrada na sidebar: staffSecondaryNav (nav.ts) num SidebarGroup com mt-auto — colado no rodapé da navegação, acima do bloco de perfil/sair, fora da staffNav e fora da barra inferior do mobile. getCampaignSecondaryNav devolve vazio para leader, então a liderança não vê um link que só a devolveria para /campanha. O markup do item virou CampaignSidebarLink (dois pontos de uso: destinos e referência).
Também corrigido no caminho (defeito de E8 achado ao conferir a redação contra a tela): formatElectionNumber usava Intl.NumberFormat('pt-BR') sem fixar dígitos fracionários, então a meta sugerida decomposta (fracionária) aparecia como "Meta usada na conta: 100,968" — lida como cem mil por qualquer humano na sala. Agora maximumFractionDigits: 0, com teste em tests/unit/goalCoverage.unit.spec.ts.
Cobertura: tests/unit/campaignIntelligenceConcepts.unit.spec.ts (ids únicos/anchor-safe, toda categoria renderizada, campos obrigatórios) e tests/e2e/campaignConcepts.e2e.spec.ts (staff lê a página e chega nela pelo card; âncora do "Saiba mais"; leader é redirecionado). /campanha/conceitos entrou na lista de prewarm de tests/e2e/setup.e2e.spec.ts: sem isso o clique no link do Popover espera o dev server compilar a rota e a asserção de URL estoura antes da navegação — flake do ambiente de teste, não do produto.
Referências
src/components/campaign/MunicipalityGoalAccountCard.tsx—GoalAccountMetric,formatRollOff,CampaignInfoHintdo título: fonte da redação v1 e ponto de integração dos links "saiba mais"src/components/campaign/CampaignInfoHint.tsx— padrão de Popover "?" já usado no título do card (precedente de affordance, distinto do Tooltip inline)src/components/campaign/nav.ts—staffNav/getCampaignNav: por que este item não entra ali agora (Adiado com gatilho)src/components/campaign/CampaignPageShell.tsx— shell de página a reusarsrc/utilities/municipalityPotential.ts,src/utilities/goalCoverage.ts— fórmulas reais a descrever com precisãosrc/lib/campoParties.ts— curadoria "campo por ano" (conceito "share intracampo")src/app/(campaign)/campanha/(app)/planos/page.tsx— precedente do gate staff-only (getCampaignUser+isCampaignStaff+redirect)docs/plans/conta-da-cadeira.md— plano de E8, seção "Follow-up pós-entrega" com a origem exata deste pedidodocs/plans/inteligencia-campanha.md— plano-mestre do programa; cada fatia futura (E9–E17) estende o array de conceitos deste item- AGENTS.md — naming em inglês/strings pt-BR,
src/libclient-safe vssrc/utilitiesserver-coupled, Campaign auth (isCampaignStaff)