diff --git a/.cursor/rules/projects/nucleos-eleitorais.mdc b/.cursor/rules/projects/nucleos-eleitorais.mdc index 27f8a866..1183ddc0 100644 --- a/.cursor/rules/projects/nucleos-eleitorais.mdc +++ b/.cursor/rules/projects/nucleos-eleitorais.mdc @@ -7,7 +7,8 @@ alwaysApply: false ## Status -- **Sessão observada CG 2026-07-29 (Bloco B):** 1º hands-on ~54 min. Achados U1–U8 / O9–O11 — não rola; mental model **Ação→Local→Quem**; confirmações invisíveis; forms precisam ser contínuos; hit = lista sem assessor × votos 2022; mapa ≠ mesa de trabalho. Pivot: tabelas=bulk; operação=fluxos lineares tipo Nubank. Snapshot [`sessao-observada-coordenador-2026-07-29-snapshot.md`](../../../docs/plans/sessao-observada-coordenador-2026-07-29-snapshot.md); CUSTOMER.md/OST atualizados. Prazo soft: manejável até reunião assessoria **03/08**. +- **B43 entregue em código (2026-07-29):** chassis UX-1 — `/campanha` fica só shell (`CampaignPageShell` vazio); dashboard staff move para `/campanha/quadro` (corpo idêntico ao antigo Início); `LeaderContactsPanel` em `/campanha/contatos`; nav staff ganha **Quadro** (sidebar; fora do bottom nav mobile); liderança **Início** + **Meus contatos**; `requireCampaignPageActor` redireciona leader bloqueado para `/campanha/contatos` (não para o Início blank). Sem migration. Plano: [`inicio-em-branco-quadro.md`](../../../docs/plans/inicio-em-branco-quadro.md). Desbloqueia **B44** / **B45**. +- **Sessão observada CG 2026-07-29 (Bloco B):** 1º hands-on ~54 min. Achados U1–U8 / O9–O11 — não rola; mental model **Ação→Local→Quem**; confirmações invisíveis; forms precisam ser contínuos; hit = lista sem assessor × votos 2022; mapa ≠ mesa de trabalho. Pivot: tabelas=bulk; operação=fluxos lineares tipo Nubank. Snapshot [`sessao-observada-coordenador-2026-07-29-snapshot.md`](../../../docs/plans/sessao-observada-coordenador-2026-07-29-snapshot.md); CUSTOMER.md/OST atualizados. Prazo soft: manejável até reunião assessoria **03/08**. **Chassis:** ~~B43~~ ✓ → **B44** botão/strip → **B45** catálogo por persona (wizards depois). - **E12+ entregue em código (2026-07-29):** escala/DRY pós-E12 — F1 forçada: `src/lib/metropolitanoTerritoryPeers.ts` unifica constantes, filtros Salvador vs demais RMS e `catalogPeersForSlug` (rollup E17/E12, `slugsForMetropolitanoSubRow`, benchmark T4). F2 não entrou — `benchmarkBySlug` já memoiza por slug. Spec `metropolitanoTerritoryPeers.unit.spec.ts`. Sem migration/UI. Plano: [`escala-dry-pos-e12.md`](../../../docs/plans/escala-dry-pos-e12.md). - **B17+ entregue em código (2026-07-29):** Escala/DRY pós-B17 — F1: `readCampaignColumnVisibility` antes do `Promise.all` e `loadLeadershipOptions` / `loadStateDeputyOptions` só quando a coluna de relação está visível (`resolveVisibleColumns`). F2: `CampaignListEmptyState` na prop `empty` de `CampaignTable` (e `SupporterList`) em demandas, organizações e apoiadores, para o botão "Colunas" sobreviver com zero linhas. Plano: [`escala-dry-pos-b17.md`](../../../docs/plans/escala-dry-pos-b17.md). - **Fill-in remover coluna Tipo entregue em código (2026-07-29):** em `/campanha/municipios`, sumiram a coluna "Tipo", o filtro single-select no header (B16), o ramo mobile em `MunicipalityFilters`, a sort key `kind` e o param `?kind=` em `municipalityListUrl` / `buildMunicipalityListWhere` / `payloadSortFieldByKey` — bookmarks antigos ignoram `kind` no parse. `municipalityKindLabels` permanece para detalhe, mapa e dossiê. `MunicipalityHeaderFilter` ficou só com filtros multi (`singleRows`/`applyMunicipalityKindFilter` deletados). B17: 11 colunas staff; e2e do picker usa Território; B18 usa `?priority=alta`. Sem migration/action/Consent. Plano: [`remover-coluna-tipo-municipios.md`](../../../docs/plans/remover-coluna-tipo-municipios.md). diff --git a/docs/plans/botao-acao-inicio-strip.md b/docs/plans/botao-acao-inicio-strip.md new file mode 100644 index 00000000..214e700a --- /dev/null +++ b/docs/plans/botao-acao-inicio-strip.md @@ -0,0 +1,103 @@ +# Botão de ação do Início + lista horizontal + +Status: rascunho +Atualizado em: 2026-07-29 +Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B44 — chassis UX-1) +Impeccable: C — primitivo UI novo (botão circular + strip rolável) no Início blank +Appetite: ~1 dia eng; 2 componentes client + long-press + Tooltip/Drawer; sem migration +Responsável: — + +## Design (Impeccable) + +Âncoras: `PRODUCT.md` (Clarity under pressure; Feel the action; anti spreadsheet/dashboard cards) / `DESIGN.md` (tokens `muted` / `foreground` / `primary` — **não** inventar cinza fora do tema) · `CampaignHoverTooltip` + `Drawer` shadcn · tema `campaign`. + +Na implementação: **shape → craft → critique → polish** (classe C). + +Brief compacto: + +- **Persona / contexto:** CG no notebook e no celular; descobre o próximo gesto **above-the-fold** sem rolar a página. +- **Job principal:** um alvo tocável (círculo + rótulo) que comunica a ação; opcionalmente explica no hover / long-press **sem** disparar a ação no long-press. +- **Estratégia de cor:** Restrained — círculo `bg-muted` (cinza claro do tema), ícone e texto `foreground` / preto do tema; sem Signal Red no idle. +- **Edit where you see:** não — launcher, não editor. +- **Anti-goals:** card com borda/sombra envolvendo o botão; ícone sem verbo; scroll do `body` quando a strip estoura; Tooltip Radix no lugar do Drawer em coarse pointer; long-press = click. + +## Dados → decisão → apresentação + +Dados: N/A — chrome de ação; catálogo de rótulos é **B45**. + +## Contexto + +Com **B43**, `/campanha` está vazio. O rascunho UX-1 pede bloco “O que você quer fazer?” com 4–6 botões. Este item entrega o **primitivo reutilizável** e a **strip horizontal** com scroll interno — ainda sem catálogo por role (B45) e sem wizards. + +Pedido (2026-07-29): círculo cinza claro + texto preto; ícone no centro; texto descritivo abaixo; área inteira clicável; lista horizontal com drag/scroll interno; descrição opcional em hover (desktop/tablet = tooltip) ou long-press (mobile = bottom drawer); long-press não dispara o click/tap. + +## Objetivos + +- Componente **`CampaignHomeActionButton`** (nome final no PR) com props: `label`, `icon` (Lucide), `description?`, `onClick?` / `href?`, `disabled?`. +- Visual: círculo claro + ícone centrado + label abaixo; hit target = círculo **e** label (um só controle focável). +- **`CampaignHomeActionStrip`**: fila horizontal; `overflow-x-auto` (não `overflow` no body); preferir scroll-snap leve; touch-pan-x; scrollbar discreta ou hidden com affordance de overflow. +- Descrição opcional: + - `pointer-fine` + hover/focus → Tooltip (reusar stack shadcn; **não** reutilizar às cegas `CampaignHoverTooltip` se o contrato touch-toggle conflitar com long-press — medir e documentar). + - `pointer-coarse` → long-press abre **Drawer** inferior com título = label + corpo = description; soltar após limiar **não** chama `onClick`. +- Sem migration / collection / Consent / action de escrita. +- Unit (jsdom): long-press não dispara click; strip não propaga scroll vertical do body (smoke); a11y name = label (+ description via `aria-describedby` quando aberta). + +## Decisões travadas + +- **Um botão = um controle**, não círculo link + texto link separados. **Rejeitado:** dois alvos (ícone vs texto) — falha de hit area e SC 2.5.3. +- **Long-press ≠ click.** Timer ~400–500 ms; `pointercancel` / move além de slop cancela; no fire do long-press, `preventDefault` no click sintético seguinte. **Rejeitado:** tap curto abre Drawer (rouba a ação futura); só `title=` nativo (inacessível no touch). +- **Tooltip fine / Drawer coarse** — espelha B34/B42 (política por pointer, não só viewport). **Rejeitado:** um único Popover; hover no mobile. +- **Cores via tokens** (`bg-muted`, `text-foreground`) — “cinza claro / preto” do pedido mapeiam ao tema, não hex soltos. **Rejeitado:** `#e5e5e5` hardcoded. +- **i18n:** ids/props em inglês (`label`, `description`, `HomeActionButton`); copy pt-BR vem do catálogo B45. + +## Questões em aberto + +- **Scroll com drag de mouse (desktop) além da wheel/trackpad?** **Opções:** A só overflow nativo | B drag-to-scroll. **Recomendação:** A no v1 — nativo basta; drag é polish se sessão reclamar. _(assumido)_ + +## Abordagem proposta + +```mermaid +flowchart TB + Strip["CampaignHomeActionStrip
overflow-x-auto"] + Btn["CampaignHomeActionButton"] + Tip["Tooltip fine pointer"] + Draw["Drawer coarse + long-press"] + Strip --> Btn + Btn --> Tip + Btn --> Draw +``` + +Componentes: + +- **`src/components/campaign/dashboard/CampaignHomeActionButton.tsx`** (`'use client'`): botão/`Link` polimórfico; círculo `size-*` com ícone; label `text-sm` centrado; long-press machine; Tooltip vs Drawer. +- **`src/components/campaign/dashboard/CampaignHomeActionStrip.tsx`**: lista `role="list"` / items; classes de overflow; opcional fade edges (Adiado se custar). +- **Não** montar no Início com dados reais aqui — story/fixture no plano de teste **ou** mount placeholder só em storybook-less: B45 monta. Em B44, e2e/unit montam os componentes isolados **ou** uma página de smoke se necessário; preferir specs de componente. +- **Migration:** Sem migration, sem collection, sem server action. + +## Dependências + +- Dura: **B43** (Início blank onde a strip pousa — pode desenvolver isolado, mas aceitação visual é no Início). +- Soft: padrão Drawer de `CampaignCellEditOverlay` / `ui/Drawer`. + +## Não escopo + +- Catálogo por role e mount no Início → **B45**. +- Navegação/wizards reais → fatias UX-1 seguintes. +- Redesign do Quadro. + +## Rabbit holes + +- **Generalizar `CampaignHoverTooltip` para long-press+Drawer.** 3º comportamento num componente já denso. **Mitigação:** lógica no botão do Início; extrair só no 2º call site idêntico. +- **Carousel com setas / lib de swipe.** **Mitigação:** overflow nativo. + +## Adiado com gatilho + +- **Drag-to-scroll no desktop.** Revisitar se CG não descobrir overflow na strip. +- **Fade edges** na strip. Revisitar no critique Impeccable se o corte visual mentir “acabou”. + +## Referências + +- `docs/roadmap.md` (B44) · [inicio-em-branco-quadro.md](inicio-em-branco-quadro.md) (B43) · [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) +- `src/components/ui/Drawer.tsx` · `tooltip.tsx` · `CampaignHoverTooltip.tsx` (contrato touch — comparar, não copiar cego) +- `PRODUCT.md` / `DESIGN.md` +- AGENTS.md — client boundary; Feel the action diff --git a/docs/plans/catalogo-acoes-inicio-por-persona.md b/docs/plans/catalogo-acoes-inicio-por-persona.md new file mode 100644 index 00000000..3ab89aec --- /dev/null +++ b/docs/plans/catalogo-acoes-inicio-por-persona.md @@ -0,0 +1,124 @@ +# Catálogo de ações do Início por persona + +Status: rascunho +Atualizado em: 2026-07-29 +Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B45 — chassis UX-1) +Impeccable: B — mount da strip B44 no Início com catálogo por role +Appetite: ~0,5 dia eng; módulo client-safe + page wire-up; 2 atalhos com href; resto inerte +Responsável: — + +## Design (Impeccable) + +Âncoras: `PRODUCT.md` (Clarity; Depth for staff / light for leader) / `DESIGN.md` · primitivo **B44** · catálogo confirmado com produto 2026-07-29. + +Na implementação: craft compacto → critique → polish. + +Brief compacto: + +- **Persona / contexto:** quatro roles veem o Início; cada um vê só ações do seu mundo (leader lockdown). +- **Job principal:** escolher a próxima ação pelo verbo — ainda sem executar wizards (exceto atalhos de lista/contatos). +- **Estratégia de cor:** herda B44 (Restrained). +- **Edit where you see:** não nesta fatia (launchers). +- **Anti-goals:** botões de inteligência/jargão (“LQ”, “captura”); >6 ações staff; leader vendo ações de município. + +## Dados → decisão → apresentação + +Dados: N/A — rótulos/ícones/descrições estáticos; sem KPI. + +## Contexto + +**B43** deixou `/campanha` vazio; **B44** entregou botão + strip. Falta o **conteúdo por persona** confirmado com produto (gate 2026-07-29) e o mount. Wizards com escrita ficam fora — botões inertes (`type="button"` sem handler, ou `aria-disabled` com toast “Em breve” — **recomendação:** inertes sem toast para não treinar clique inútil; `aria-disabled` + explicação só se critique exigir feedback). + +Alinhado a [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) A1–A6 + lockdown leader. + +## Objetivos + +- Módulo client-safe `src/lib/campaignHomeActions.ts` (ou `dashboard/`) com: + - tipo `CampaignHomeAction = { id, label, icon, description, href?: string }` + - `homeActionsForRole(role): readonly CampaignHomeAction[]` +- Montar `CampaignHomeActionStrip` em `/campanha` (RSC passa a lista resolvida por `user.role`). +- **Atalhos com destino real nesta fatia:** + - staff `uncovered-municipalities` → `buildMunicipalityListHref({ coverage: 'sem_assessor', sort: 'votos' }, 1)` (serializar no server — não importar o builder no client strip se arrastar catálogo; passar `href` já pronto). + - leader `my-contacts` → `/campanha/contatos` +- Demais ações: **sem** `href`/`onClick` (inerte). +- Assessor: mesmos ids/rótulos; descriptions com “nos municípios da sua carteira” onde couber. +- Sem migration / Consent / action de escrita. + +## Catálogo confirmado (produto 2026-07-29) + +### Coordenador Geral e Candidato + +| id | Rótulo | Ícone | Descrição | +| -------------------------- | ------------------------------- | ----------------- | ---------------------------------------------------------------------- | +| `update-votes` | Atualizar votos de um município | `BarChart3` | Atualizar a projeção (média, pessimista, otimista) de um município | +| `register-signal` | Registrar o que aconteceu | `Megaphone` | Anotar sinal urgente: invasão, perda de apoio, novo apoio, dificuldade | +| `change-trend` | Mudar tendência | `TrendingUp` | Favorável, neutra ou desfavorável — com o porquê | +| `update-leadership` | Atualizar liderança | `Handshake` | Trocar quem coordena, status de apoio ou votos que a pessoa declara | +| `register-demand` | Registrar pedido | `Inbox` | Demanda de material, transporte, diária ou outro pedido do município | +| `uncovered-municipalities` | Ver quem ainda não está coberto | `UserRoundSearch` | Abrir a lista de municípios sem assessor (atalho — não é wizard) | + +### Assessor + +Mesmos 6 ids/rótulos/ícones. Descrições: acrescentar escopo de carteira (ex.: “…nos municípios da sua carteira”). `uncovered-municipalities` usa o mesmo href; access da lista já filtra. + +### Liderança + +| id | Rótulo | Ícone | Descrição | +| -------------------- | ------------------ | ---------- | ----------------------------------------------- | +| `register-supporter` | Cadastrar apoiador | `UserPlus` | Registrar alguém da sua rede que apoia o Solla | +| `my-contacts` | Ver meus contatos | `Users` | Abrir a lista dos apoiadores que você cadastrou | + +## Decisões travadas + +- **Catálogo em `lib/` puro**, não Payload. **Rejeitado:** collection de “ações” (caro, zero ganho até wizards). +- **CG ≡ candidate** no conjunto de botões (`isCampaignUnrestricted` / ambos staff unrestricted). **Rejeitado:** botões só no CG (candidate tem a mesma mesa). +- **Dois botões na liderança; `my-contacts` navega já.** **Rejeitado:** só um botão; os dois inertes (contatos já têm rota B43 — esconder o atalho reintroduce atrito). +- **`uncovered-municipalities` navega já** (A6 do rascunho — não é wizard). **Rejeitado:** esperar wizard chassis. +- **i18n:** ids estáveis em inglês (`update-votes`, …); labels pt-BR no catálogo. + +## Questões em aberto + +- **Feedback ao tocar ação inerte?** **Opções:** A silêncio | B toast “Em breve”. **Recomendação:** A — critique pode pedir B. _(assumido — validar no polish)_ + +## Abordagem proposta + +```mermaid +flowchart LR + Page["/campanha RSC"] --> Cat["homeActionsForRole(role)"] + Cat --> Strip["CampaignHomeActionStrip"] + Strip --> Btn["CampaignHomeActionButton × N"] +``` + +Componentes: + +- **`src/lib/campaignHomeActions.ts`:** catálogo + `homeActionsForRole`. +- **`page.tsx` (Início):** após B43 blank, monta strip com actions do ator; resolve hrefs server-side. +- **Testes:** unit do catálogo (cada role → ids esperados; leader sem ids de município; staff tem `uncovered-municipalities` com href shape); e2e smoke: Início staff mostra 6 botões; leader 2; click em “Ver meus contatos” abre `/campanha/contatos`. +- **Migration:** Sem migration, sem collection, sem server action. + +## Dependências + +- Duras: **B43**, **B44**. +- Soft: rascunho UX-1; `buildMunicipalityListHref`. + +## Não escopo + +- Chassis/wizard de passos e escrita A1–A5 → próximos itens UX-1 (registrar após B45). +- Ligar E11 → wizards. +- Voltar mapa/KPI à 1ª dobra. + +## Rabbit holes + +- **Feature-flag por ação / remote config.** **Mitigação:** const no `lib/` até o 2º mandato precisar. +- **Toast “Em breve” + analytics por idle click.** **Mitigação:** silêncio até critique. + +## Adiado com gatilho + +- **Wire real de cada id → rota de wizard.** Revisitar item a item (A1 votos primeiro — ordem do rascunho UX-1). +- **Toast em inerte.** Revisitar se sessão mostrar taps repetidos confusos. + +## Referências + +- `docs/roadmap.md` (B45) · [inicio-em-branco-quadro.md](inicio-em-branco-quadro.md) · [botao-acao-inicio-strip.md](botao-acao-inicio-strip.md) · [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) +- `src/utilities/municipality/municipalityListUrl.ts` (`coverage: 'sem_assessor'`) +- `PRODUCT.md` / `DESIGN.md` · AGENTS.md — roles / lockdown leader diff --git a/docs/plans/fluxos-acao-primeiro-inicio.md b/docs/plans/fluxos-acao-primeiro-inicio.md index 757d7d8b..bc2fc86b 100644 --- a/docs/plans/fluxos-acao-primeiro-inicio.md +++ b/docs/plans/fluxos-acao-primeiro-inicio.md @@ -206,16 +206,24 @@ Não competir com os botões. --- -## Ordem de entrega sugerida (ainda planejamento) +## Ordem de entrega sugerida -1. **Chassis do wizard** (shell de passos + busca de município + CTA final + resumo) -2. **A1** (votos) com “quer também?” sinal + tendência -3. **A6** atalho + tabela above-the-fold na lista -4. **A2** standalone (se não ficou bom só embutido) -5. **A4** curto (status / observação / quem coordena) -6. **A5** + edição de demanda -7. Ligar E11 → wizards -8. Copy pass + A3 standalone se ainda fizer falta +### Chassis do Início (registrado 2026-07-29) + +0. **B43** Início em branco + Quadro/Contatos — [inicio-em-branco-quadro.md](inicio-em-branco-quadro.md) +1. **B44** Botão circular + strip horizontal — [botao-acao-inicio-strip.md](botao-acao-inicio-strip.md) +2. **B45** Catálogo por persona + botões (atalhos reais; wizards ainda inertes) — [catalogo-acoes-inicio-por-persona.md](catalogo-acoes-inicio-por-persona.md) + +### Wizards (ainda planejamento — IDs depois de B45) + +3. **Chassis do wizard** (shell de passos + busca de município + CTA final + resumo) +4. **A1** (votos) com “quer também?” sinal + tendência +5. **A6** — atalho já parcialmente em B45 (`uncovered-municipalities`); tabela above-the-fold na lista se ainda faltar +6. **A2** standalone (se não ficou bom só embutido) +7. **A4** curto (status / observação / quem coordena) +8. **A5** + edição de demanda +9. Ligar E11 → wizards +10. Copy pass + A3 standalone se ainda fizer falta --- diff --git a/docs/plans/inicio-em-branco-quadro.md b/docs/plans/inicio-em-branco-quadro.md new file mode 100644 index 00000000..3f8013de --- /dev/null +++ b/docs/plans/inicio-em-branco-quadro.md @@ -0,0 +1,103 @@ +# Início em branco + Quadro (preservar dashboard) + +Status: entregue (2026-07-29) +Atualizado em: 2026-07-29 — as-built: redirect de leader nos gates → `/campanha/contatos`; loading do quadro em `quadro/loading.tsx`; Início com `CampaignPageShell` vazio. +Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B43 — chassis UX-1) +Impeccable: B — relocate do dashboard existente + Início intencionalmente vazio sob tema `campaign` +Appetite: ~0,5 dia eng; move de rota + nav + blank home; sem migration +Responsável: — + +## Design (Impeccable) + +Âncoras: `PRODUCT.md` (princípios 2 Clarity under pressure, 5 Depth/simplicity — leader path; anti-goals §5 dashboard SaaS) / `DESIGN.md` (register `product`, Field Desk) · tema `data-theme='campaign'` · shell `CampaignPageShell` / `nav.ts`. + +Na implementação (`implement-roadmap-item`): craft compacto → critique → polish. `harden`/`optimize` só se Passo 8 acionar gatilho. + +Brief compacto: + +- **Persona / contexto:** CG (e demais papéis) abrem o app sob pressão de Zap; a tela atual começa por mapa/KPI e compete com o ritual Ação→Local→Quem. +- **Job principal:** `/campanha` vira superfície vazia pronta para o bloco de ações (B44/B45); o quadro operacional sobrevive intacto em URL própria. +- **Estratégia de cor:** Restrained. +- **Edit where you see:** não — só relocate / blank; sem edição nova. +- **Anti-goals:** redesign do dashboard nesta fatia; saudação/título no Início “só um pouquinho”; segundo home paralelo; quebrar o lockdown da liderança (contatos sem destino). + +## Dados → decisão → apresentação + +Dados: N/A — relocate e blank; nenhum número novo nesta entrega. + +## Contexto + +O Início staff (`src/app/(campaign)/campanha/(app)/page.tsx` → `CampaignDashboard`) monta mapa, KPIs, sugestões E11, prioritários, visitados, “Onde estou”. A liderança recebe `LeaderContactsPanel` no mesmo path. A sessão observada 2026-07-29 e o rascunho [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) (UX-1) pedem **ações first**; o quadro atual continua útil como **briefing / gestão**, não como porta de entrada. + +Pedido de produto (2026-07-29): primeira tarefa do remodel — estruturar sem perder o que existe (exemplo vivo), com Início **completamente em branco** (só sidebar). + +## Objetivos + +- `/campanha` renderiza só o chrome do `(app)` (sidebar / bottom nav) — **zero** `h1`, subtítulo, mapa, KPI, slots. +- Dashboard staff atual move para **`/campanha/quadro`** com comportamento byte-equivalente (mesmos loaders/slots). +- Contatos da liderança move para **`/campanha/contatos`** (hoje vivem no Início). +- Sidebar / bottom nav: entrada **Quadro** (staff); liderança ganha **Início** + **Meus contatos** (hrefs distintos — sem React key duplicada). +- Sem migration, collection, Consent, server action nova. +- `isCampaignNavActive('/campanha')` continua match exato (não engole `/quadro` / `/contatos`). + +## Decisões travadas + +- **`/campanha/quadro` para o dashboard staff.** Copy da UI já fala “Quadro geral / dos municípios”. **Rejeitado:** `/visao-geral` (mais longo); `/inicio-antigo` (temporário no URL); deixar dashboard em `/campanha` e ações em subrota (contradiz o pedido de Início blank). +- **`/campanha/contatos` para `LeaderContactsPanel`.** O blank do Início **não** pode orphanar a única ferramenta da liderança. **Rejeitado:** manter contatos no Início “só para leader” (dois Inícios mentais); esconder contatos até B45 (lockdown quebrado por um dia). +- **Início literalmente vazio** — sem saudação. **Rejeitado:** “Olá, {nome}” residual (vira âncora visual que o bloco de ações depois displace). +- **i18n:** identificadores `quadro` / `contatos` nas pastas de rota; strings “Quadro” / “Meus contatos” / “Início” em pt-BR. + +## Questões em aberto + +- **Redirect de bookmarks `/campanha` que esperavam o mapa?** **Opções:** A nada | B redirect staff→`/quadro`. **Recomendação:** A — o Início novo é o destino; Quadro está a um clique na sidebar. _(assumido)_ + +## Abordagem proposta + +```mermaid +flowchart LR + Home["/campanha page.tsx
blank shell"] + Quadro["/campanha/quadro
CampaignDashboard + slots"] + Contatos["/campanha/contatos
LeaderContactsPanel"] + Nav["nav.ts
Início + Quadro / Contatos"] + Home --> Nav + Quadro --> Nav + Contatos --> Nav +``` + +Componentes: + +- **`page.tsx` (Início)** — `requireCampaignPageActor()`; return `` vazio (ou fragmento sem filhos de conteúdo). Sem branch leader/staff de dados. +- **`quadro/page.tsx` (novo)** — corpo atual do home staff (dashboard + Suspense map/suggestions). Gate: staff only; leader → redirect `/campanha/contatos` ou `/campanha`. +- **`contatos/page.tsx` (novo)** — corpo atual do branch `isCampaignLeader` + `LeaderContactsPanel`. Staff que abrir → redirect `/campanha` ou 404 de rota (recomendação: redirect `/campanha`). +- **`nav.ts`:** staff: Início `/campanha`, Quadro `/campanha/quadro` (ícone ex. `LayoutDashboard`), demais intactos. Leader: Início `/campanha` + Meus contatos `/campanha/contatos`. +- **`getCampaignBottomNav`:** incluir Quadro ou não no cap de 5 — **recomendação:** manter Início + municípios…; Quadro só sidebar no mobile (mapa/KPI não é ritual de polegar). Documentar no PR. +- **Migration:** Sem migration, sem collection, sem server action. + +## Dependências + +- Nenhuma de outro plano duro. Soft: UX-1 / [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md). Desbloqueia **B44** / **B45**. + +## Não escopo + +- Bloco de ações / botões → **B44** / **B45**. +- Wizards com escrita → fatias posteriores de UX-1. +- Redesign visual do `CampaignDashboard`. +- Mover mapa/KPIs de volta ao Início “abaixo das ações” (2ª dobra do rascunho UX-1 — gatilho pós-B45 + uso real). + +## Rabbit holes + +- **Reescrever `CampaignDashboard` “já que estamos mexendo”.** Explosão de polish. **Mitigação:** move mecânico; zero mudança de markup do quadro. +- **Unificar Quadro+Contatos numa rota com `?role=`.** Confunde URL e access. **Mitigação:** duas rotas explícitas. + +## Adiado com gatilho + +- **2ª dobra no Início** (mapa/KPI/sugestões sob as ações). Revisitar quando: B45 entregue **e** CG pedir briefing no mesmo viewport (soft 03/08). +- **Quadro no bottom nav mobile.** Revisitar se analytics/sessão mostrarem acesso frequente ao mapa no telefone. + +## Referências + +- `docs/roadmap.md` (UX-1; B43) +- [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) · [sessao-observada-coordenador-2026-07-29-snapshot.md](sessao-observada-coordenador-2026-07-29-snapshot.md) +- `src/app/(campaign)/campanha/(app)/page.tsx` · `CampaignDashboard.tsx` · `LeaderContactsPanel.tsx` · `shell/nav.ts` +- `PRODUCT.md` / `DESIGN.md` — Field Desk +- AGENTS.md — Campaign auth; naming (URL pt-BR / ids inglês) diff --git a/docs/roadmap.md b/docs/roadmap.md index bf1201bb..57627a5f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap — Teqo -Atualizado em: 2026-07-29 +Atualizado em: 2026-07-29 — B43 entregue (chassis UX-1); B44–B45 pendentes. 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) @@ -83,7 +83,10 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re ### Demais itens abertos -- **UX-1 fluxos ação-primeiro (prioridade pós-sessão 29/07)** — evidência: [snapshot](plans/sessao-observada-coordenador-2026-07-29-snapshot.md) · O9–O11 em CUSTOMER.md · **rascunho de ações/fluxos:** [fluxos-acao-primeiro-inicio.md](plans/fluxos-acao-primeiro-inicio.md). CG pensa **Ação → Local → Quem**; não rola; não clica Salvar/Aprovar; tenta editar onde vê (U9); quer formulário contínuo. Bugs de sessão: demanda sem editar título (U10); filtro município na lista (U11). Soft deadline: **03/08**. +- **UX-1 fluxos ação-primeiro (prioridade pós-sessão 29/07)** — evidência: [snapshot](plans/sessao-observada-coordenador-2026-07-29-snapshot.md) · O9–O11 em CUSTOMER.md · **rascunho:** [fluxos-acao-primeiro-inicio.md](plans/fluxos-acao-primeiro-inicio.md). Soft deadline: **03/08**. **Chassis:** ~~**B43**~~ ✓ → **B44** → **B45** (botão/strip + catálogo; wizards com escrita = fatias seguintes). +- ~~**B43** Início em branco + Quadro~~ — **entregue 2026-07-29** (`/campanha` blank; staff dashboard em `/campanha/quadro`; liderança em `/campanha/contatos`; nav Início+Quadro / Início+Meus contatos; `requireCampaignPageActor` manda leader bloqueado → `/campanha/contatos`; sem migration) · [plano](plans/inicio-em-branco-quadro.md) +- **B44** Botão de ação do Início + lista horizontal — círculo `muted` + ícone + label; strip com scroll interno; descrição opcional Tooltip (fine) / Drawer via long-press (coarse; long-press ≠ click). Depende de B43 · ~1d · Janela 1 · Impeccable C · [plano](plans/botao-acao-inicio-strip.md) +- **B45** Catálogo de ações por persona — CG/candidato e assessor (6 ações; `uncovered-municipalities` já navega); liderança (2; `my-contacts` → `/campanha/contatos`); resto inerte até wizards. Depende de B44 · ~0,5d · Janela 1 · Impeccable B · [plano](plans/catalogo-acoes-inicio-por-persona.md) - ~~**E4R** import único da planilha de projeção~~ — **entregue 2026-07-24** (`pnpm db:seed:projecao`; overwrite-always; [plano](plans/import-planilha-projecao.md)) - ~~**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) @@ -99,7 +102,7 @@ O discovery literatura→persona→entrevista ([relatório aprovado](research/re - **D3** fundação multi-sessão (QR por `campaignUser` + log + Consent `campanha-whatsapp-canal`) · [plano](plans/whatsapp-canal-fundacao.md) - **D4** envio 1:1 pela sessão do ator · depende de D3 · [plano](plans/whatsapp-envio-liderancas.md) - **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 débitos FD2 ([field-desk-ux-pos-critique.md](plans/field-desk-ux-pos-critique.md)): glossário (O3 **confirmada 29/07**), triagem em lote, empty states · **subordinado a UX-1** (não polish células se a porta de entrada continuar entidade-primeiro) · gatilho: antes de 16/08; soft 03/08 +- **R6** critique/polish visual da vertical remodelada (ciclo /impeccable completo por superfície; smoke visual coordenador feito em 2026-07-21) · absorve débitos FD2 ([field-desk-ux-pos-critique.md](plans/field-desk-ux-pos-critique.md)): glossário (O3 **confirmada 29/07**), triagem em lote, empty states · **subordinado a UX-1 / B43–B45** (não polish células se a porta de entrada continuar entidade-primeiro) · gatilho: antes de 16/08; soft 03/08 - ~~**B38** sidebar recolhido em tablet~~ — **entregue 2026-07-26** (`collapsible="offcanvas"` no `CampaignSidebar`; `SidebarTrigger` no inset `md+`; `defaultOpen` lê cookie `sidebar_state`; `CampaignSidebarViewportDefault` colapsa uma vez em 768–1023 sem cookie; mobile Sheet inalterado; sem migration) · [plano](plans/sidebar-recolhido-tablet.md) - ~~**B14**~~ ✓ município mais próximo (geolocalização → card "Onde estou" no Início staff, entregue 2026-07-26; **débito B8 F2-D1** ZE exata em Salvador fechado 2026-07-29) · pede permissão **1× por sessão** se ainda não concedida; matching client-side **por point-in-polygon** (malha municipal + malha das 19 ZE em Salvador; centroides ordenam fallback de carteira e ZE mais próxima) · [plano](plans/municipio-mais-proximo.md) - ~~**B20** Municípios prioritários no Início~~ — **entregue 2026-07-25** (`pickDashboardPriorityMunicipalities` + amostra 8 por déficit `central`; Badge com total; “Ver todas” → `?priority=alta`; `list-none` na strip) · [plano](plans/municipios-prioritarios-inicio.md) @@ -184,6 +187,9 @@ flowchart TD B40n["B40 ✓ Login biométrico
WebAuthn"] B41n["B41 ✓ Scroll horizontal
+ sticky Município"] B42n["B42 ✓ Polimento mobile
lista municípios"] + B43n["B43 ✓ Início em branco
+ Quadro / Contatos"] + B44n["B44 Botão ação
+ strip horizontal"] + B45n["B45 Catálogo ações
por persona"] B15n["B15 ✓ Ordenar lista
por coluna"] B16n["B16 ✓ Filtros no header
da lista"] B17n["B17 ✓ Seletor de colunas
das 7 listas"] @@ -246,6 +252,7 @@ flowchart TD B26n -.Drawer mobile.-> B42n B24n -.Trend control.-> B42n B27n -.Advisors control.-> B42n + B43n --> B44n --> B45n B16n -.densidade do th.-> B22n E18n -.oneLiner dos conceitos.-> B22n B22n -.colunas se explicam.-> B17n @@ -337,11 +344,11 @@ flowchart TD B8F2 -.rank por ZE = débito aberto.-> B14n ``` -Paralelizáveis agora: **B13** (E10 ✓ entregou as classes; consome `computeMunicipalityTerritorialClass` como está), **E12** (E8 ✓), ~~**E11**~~ ✓ (motor de sugestões v1 entregue 2026-07-28), ~~**B14**~~ ✓ (atalho geo no Início entregue 2026-07-26; ~~B8 F2~~ ✓ entregou os polígonos das ZE em 2026-07-26, então desambiguar Salvador virou débito com gatilho "pedido de campo", não dependência), ~~**B38**~~ ✓ (sidebar recolhido em tablet entregue 2026-07-26), **B39** (lembrar de mim / sessão longa; sem dep dura — cookie/JWT já existem; soft B40 só pela ordem no mesmo `LoginForm`), ~~**B40**~~ ✓ (login biométrico WebAuthn — entregue 2026-07-28), ~~**B41**~~ ✓ (scroll horizontal + sticky — entregue 2026-07-27), ~~**B42**~~ ✓ (polimento mobile da mesma lista — entregue 2026-07-27), **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; a mecânica genérica e a promoção do primitivo já saíram no E10 ✓ — restam os consumidores Assessores/Tendência e a convivência com o Popover), ~~**B24**~~ ✓ (auto-save + justificativa no popover de Tendência; entregue 2026-07-26), **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; remover lista de zonas do Início — pós-B8 F2 ✓; í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 — **entregue 2026-07-26**), **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; entregue 2026-07-26), **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). +Paralelizáveis agora: ~~**B43**~~ ✓ (chassis UX-1 — entregue 2026-07-29), ~~**B42**~~ ✓ (polimento mobile da mesma lista — entregue 2026-07-27), **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; a mecânica genérica e a promoção do primitivo já saíram no E10 ✓ — restam os consumidores Assessores/Tendência e a convivência com o Popover), ~~**B24**~~ ✓ (auto-save + justificativa no popover de Tendência; entregue 2026-07-26), **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; remover lista de zonas do Início — pós-B8 F2 ✓; í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 — **entregue 2026-07-26**), **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; entregue 2026-07-26), **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; **B20 ✓** Municípios prioritários no Início entregue 2026-07-25; ~~**B38**~~ ✓ sidebar recolhido em tablet (entregue 2026-07-26 — `offcanvas` + trigger `md+` + cookie/`matchMedia`); **B39** (lembrar de mim / sessão longa — o default de 2 h está derrubando sessão no onboarding; ~0,5 dia, entra cedo) e, logo atrás se folga, ~~**B40**~~ ✓ (biometria WebAuthn — entregue 2026-07-28); ~~**B41**~~ ✓ (scroll horizontal + sticky na lista de municípios — entregue 2026-07-27) e ~~**B42**~~ ✓ (polimento mobile da mesma lista — Drawer em Tendência/Assessores/Votos estimados + card compacto e clicável; entregue 2026-07-27); **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 — entregue 2026-07-26; a mesa corrige em massa as notas semeadas pelo E4R ✓ sem abrir 435 telas de edição); **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 — entregue 2026-07-26; fecha o adiado do próprio B29); **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) entregues 2026-07-28; 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 **remover lista de zonas do Início**~~ ✓ (entregue 2026-07-28); fill-in **selecionados no topo nos filtros do header** (desmarcar sem caçar checkbox; helper puro que B21 herda — quarto de dia); **C13 ✓** (renomeado "Plano de Ação" → "Atividade" em toda a vertical, incluindo a migration data-preserving — entregue 2026-07-25); 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 entregue 2026-07-26); Onda 0 jurídica em paralelo (externa).>>>>>>> 4ad2e6f (B18: saved municipality list filters in localStorage and sidebar submenu.) +**Janela 1 — agora → 05/08 (convenções):** **prioridade pós-29/07 — ~~B43~~ ✓ → B44 → B45** (chassis UX-1: ~~Início blank + Quadro/Contatos~~ entregue 2026-07-29; botão/strip + catálogo por persona; soft 03/08) · [fluxos](plans/fluxos-acao-primeiro-inicio.md); 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 entregue 2026-07-25; ~~**B38**~~ ✓ sidebar recolhido em tablet (entregue 2026-07-26 — `offcanvas` + trigger `md+` + cookie/`matchMedia`); **B39** (lembrar de mim / sessão longa — o default de 2 h está derrubando sessão no onboarding; ~0,5 dia, entra cedo) e, logo atrás se folga, ~~**B40**~~ ✓ (biometria WebAuthn — entregue 2026-07-28); ~~**B41**~~ ✓ (scroll horizontal + sticky na lista de municípios — entregue 2026-07-27) e ~~**B42**~~ ✓ (polimento mobile da mesma lista — Drawer em Tendência/Assessores/Votos estimados + card compacto e clicável; entregue 2026-07-27); **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 — entregue 2026-07-26; a mesa corrige em massa as notas semeadas pelo E4R ✓ sem abrir 435 telas de edição); **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 — entregue 2026-07-26; fecha o adiado do próprio B29); **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) entregues 2026-07-28; 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 **remover lista de zonas do Início**~~ ✓ (entregue 2026-07-28); fill-in **selecionados no topo nos filtros do header** (desmarcar sem caçar checkbox; helper puro que B21 herda — quarto de dia); **C13 ✓** (renomeado "Plano de Ação" → "Atividade" em toda a vertical, incluindo a migration data-preserving — entregue 2026-07-25); 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 entregue 2026-07-26); 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. @@ -351,9 +358,9 @@ Paralelizáveis agora: **B13** (E10 ✓ entregou as classes; consome `computeMun ### Cortes seguros / não cortáveis -**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`); **B39** lembrar de mim / sessão longa (default de 2 h derruba sessão no onboarding/campo — atrito diário de adoção). +**Não cortáveis:** Onda 0 (jurídico/Consent); **B44–B45** chassis UX-1 (porta de entrada ação-primeiro — cortar = continuar sem botões no Início até 03/08); ~~**B43**~~ ✓ (entregue 2026-07-29); ~~**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`); **B39** lembrar de mim / sessão longa (default de 2 h derruba sessão no onboarding/campo — atrito diário de adoção). -**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~~ (entregue 2026-07-27 — `priority` continua, intocado); ~~**B8 F2** polígonos~~ (entregue 2026-07-26 — o mapa não é mais agregado em Salvador); **D2** push (adiar); **A6**; ~~**B14** município mais próximo~~ (entregue 2026-07-26); ~~**B38** sidebar recolhido em tablet~~ (entregue 2026-07-26); ~~**B40** login biométrico~~ (entregue 2026-07-28 — senha + B39 ✓ continuam, e cortar teria custado só o atalho de digital/Face ID; era o mais caro e o primeiro a cair desta leva B39–B42); **B42** polimento mobile da lista de municípios (Popover e card longos continuam usáveis — cortar custa conforto de teclado e densidade no celular); ~~**B41** scroll horizontal + sticky~~ (entregue 2026-07-27) (a tabela continua legível com zoom/pan da página — cortar custa o overflow em tablet; cai **depois** de B42 porque o bug de layout em mesa é mais caro que o polish mobile); ~~**B18** filtros salvos~~ ✓ entregue (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~~ ✓ entregue (tabela completa continua; scroll horizontal); ~~**B22** explicação no header~~ (entregue 2026-07-26); ~~**B23** tooltip nas células~~ (entregue 2026-07-26); ~~**B24** auto-save + justificativa na Tendência~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-26; a contagem `leadershipCount` era o fallback 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~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-25); **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); ~~fill-in **remover lista de zonas do Início**~~ ✓ (entregue 2026-07-28 — o mapa B8 F2 ✓ continua abrindo cada ZE); ~~**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.>>>>>>> 4ad2e6f (B18: saved municipality list filters in localStorage and sidebar submenu.) +**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~~ (entregue 2026-07-27 — `priority` continua, intocado); ~~**B8 F2** polígonos~~ (entregue 2026-07-26 — o mapa não é mais agregado em Salvador); **D2** push (adiar); **A6**; ~~**B14** município mais próximo~~ (entregue 2026-07-26); ~~**B38** sidebar recolhido em tablet~~ (entregue 2026-07-26); ~~**B40** login biométrico~~ (entregue 2026-07-28 — senha + B39 ✓ continuam, e cortar teria custado só o atalho de digital/Face ID; era o mais caro e o primeiro a cair desta leva B39–B42); **B42** polimento mobile da lista de municípios (Popover e card longos continuam usáveis — cortar custa conforto de teclado e densidade no celular); ~~**B41** scroll horizontal + sticky~~ (entregue 2026-07-27) (a tabela continua legível com zoom/pan da página — cortar custa o overflow em tablet; cai **depois** de B42 porque o bug de layout em mesa é mais caro que o polish mobile); ~~**B18** filtros salvos~~ ✓ entregue (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~~ ✓ entregue (tabela completa continua; scroll horizontal); ~~**B22** explicação no header~~ (entregue 2026-07-26); ~~**B23** tooltip nas células~~ (entregue 2026-07-26); ~~**B24** auto-save + justificativa na Tendência~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-26; a contagem `leadershipCount` era o fallback 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~~ (entregue 2026-07-26); **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~~ (entregue 2026-07-25); **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); ~~fill-in **remover lista de zonas do Início**~~ ✓ (entregue 2026-07-28 — o mapa B8 F2 ✓ continua abrindo cada ZE); ~~**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 diff --git a/src/app/(campaign)/campanha/(app)/contatos/page.tsx b/src/app/(campaign)/campanha/(app)/contatos/page.tsx new file mode 100644 index 00000000..a8cc8a89 --- /dev/null +++ b/src/app/(campaign)/campanha/(app)/contatos/page.tsx @@ -0,0 +1,31 @@ +import config from '@payload-config' +import { redirect } from 'next/navigation' +import { getPayload } from 'payload' + +import { LeaderContactsPanel } from '@/components/campaign/leadership/LeaderContactsPanel' +import { isCampaignLeader } from '@/utilities/campaignAccess' +import { requireCampaignPageActor } from '@/utilities/campaignPageActor' +import { loadLeaderContactsPageData } from '@/utilities/leaderContactsPageData' + +export const dynamic = 'force-dynamic' + +export default async function CampaignContatosPage() { + const [payload, user] = await Promise.all([getPayload({ config }), requireCampaignPageActor()]) + + if (!isCampaignLeader(user)) { + redirect('/campanha') + } + + const view = await loadLeaderContactsPageData(payload, user) + + return ( + + ) +} diff --git a/src/app/(campaign)/campanha/(app)/loading.tsx b/src/app/(campaign)/campanha/(app)/loading.tsx index 7593710b..230a3173 100644 --- a/src/app/(campaign)/campanha/(app)/loading.tsx +++ b/src/app/(campaign)/campanha/(app)/loading.tsx @@ -1,19 +1,9 @@ import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' -import { Skeleton } from '@/components/ui/skeleton' -export default function CampaignDashboardLoading() { +export default function CampaignHomeLoading() { return ( - -
- - -
- -
- - - -
+ + {null} ) } diff --git a/src/app/(campaign)/campanha/(app)/page.tsx b/src/app/(campaign)/campanha/(app)/page.tsx index 33264cc4..cd6ad9b3 100644 --- a/src/app/(campaign)/campanha/(app)/page.tsx +++ b/src/app/(campaign)/campanha/(app)/page.tsx @@ -1,139 +1,10 @@ -import config from '@payload-config' -import { getPayload, type Payload } from 'payload' -import { Suspense } from 'react' - -import { CampaignDashboard } from '@/components/campaign/dashboard/CampaignDashboard' -import { LeaderContactsPanel } from '@/components/campaign/leadership/LeaderContactsPanel' -import { MunicipalityMapPanelDynamic } from '@/components/campaign/map/MunicipalityMapPanelDynamic' -import { MunicipalityEstimateScenarioProvider } from '@/components/campaign/municipality/MunicipalityEstimateScenarioContext' -import { - SuggestionSilenceStrip, - SuggestionsPanel, -} from '@/components/campaign/suggestion/SuggestionsPanel' -import type { CampaignUser } from '@/payload-types' -import { isCampaignLeader, isCampaignStaff } from '@/utilities/campaignAccess' -import { getCampaignDashboardData } from '@/utilities/campaignDashboardData' +import { CampaignPageShell } from '@/components/campaign/shell/CampaignPageShell' import { requireCampaignPageActor } from '@/utilities/campaignPageActor' -import { loadFederalCandidateOptions } from '@/utilities/electionCandidateOptions' -import { loadLeaderContactsPageData } from '@/utilities/leaderContactsPageData' -import { loadMunicipalityMapBundle } from '@/utilities/municipality/municipalityMapData' -import { loadMunicipalitySuggestions } from '@/utilities/municipality/municipalityTriggers' - -import { resolveSuggestionFormAction } from './suggestionFormActions' export const dynamic = 'force-dynamic' -type CampaignHomePageProps = { - searchParams: Promise> -} - -export default async function CampaignHomePage({ searchParams }: CampaignHomePageProps) { - const rawSearchParams = await searchParams - const [payload, user] = await Promise.all([getPayload({ config }), requireCampaignPageActor()]) - - if (isCampaignLeader(user)) { - const view = await loadLeaderContactsPageData(payload, user) - return ( - - ) - } - - const isStaff = isCampaignStaff(user) - const view = await getCampaignDashboardData(payload, user) - - return ( -