8.4 KiB
Chassis do wizard de campanha (shell + voltar)
Status: entregue (2026-07-29)
Atualizado em: 2026-07-29 — as-built: CampaignWizardShell (shared/), rotas /campanha/acoes/<slug> (cinco entradas staff com passo ponte “Em qual município?”), campaignActionRoutes.ts + hrefs no catálogo B45; caption sticky centro; Voltar só mobile; sem migration.
Item do roadmap: docs/roadmap.md (Trilha B, item B59 — UX-1 wizards)
Impeccable: C — UI nova (shell de fluxo multi-passo sob /campanha)
Appetite: ~0,75–1 dia eng; layout de passo + header mobile + slot de conteúdo + caption sticky do município; sem migration
Responsável: —
Design (Impeccable)
Âncoras: PRODUCT.md (Clarity under pressure; Feel the action; anti spreadsheet) / DESIGN.md · tema data-theme='campaign' · shells CampaignPageShell onde couber.
Na implementação (implement-roadmap-item): shape → craft → critique → polish.
Brief compacto:
- Persona / contexto: CG/assessor no celular entre Zaps; um passo por vez, sem chrome de lista — e sem esquecer qual município está atualizando enquanto edita votos/sinal/tendência.
- Job principal: enquadrar qualquer etapa de wizard (busca, votos, sinal…) com volta óbvia no mobile, conteúdo focável e contexto de município sempre legível após a escolha.
- Estratégia de cor: Restrained.
- Edit where you see: não — fluxo linear; escrita nas etapas filhas.
- Anti-goals: sidebar/nav do app competindo com o passo; seta “voltar” no desktop (lá é browser); segundo design system de wizard; badge/chip gritante de município no meio do passo; repetir o nome do município no título de cada pergunta.
Dados → decisão → apresentação
Dados: N/A — chrome de navegação; conteúdo numérico/textual nas etapas B60–B64.
Contexto
Chassis UX-1 do Início (B43–B47 ✓) deixa ações inertes até existir wizard. O rascunho fluxos-acao-primeiro-inicio.md pede “uma coluna / um passo visível” e interrupção recuperável. Pedido de produto (2026-07-29): em mobile, seta de retorno no topo esquerdo de toda etapa; em desktop/tablet, expectativa = botão voltar do browser (sem duplicar chrome).
Este item é o frame compartilhado — não a busca, nem o ajuste de votos, nem o sinal.
Objetivos
- Componente shell (nome final no PR, ex.
CampaignWizardShell) com: título/pergunta do passo, slotchildren, slot opcional de ação no topo direito (ex. “Pular…” do B63), back control só em viewport mobile (md:hiddenou equivalente) que navega ao passo anterior / Início conforme o fluxo. - Contexto de município (pedido 2026-07-29): quando o fluxo já tem município escolhido, o nome permanece visível no limite superior central, semi-discreto (
text-sm/muted-foreground— craft), sticky no scroll do passo (não some ao rolar formulário longo). Ausente na etapa B60 (ainda não há município). Não substitui o título/pergunta do passo. - Desktop: sem seta; documentar que
history.back/ rota anterior cobre o gesto. - Layout: uma coluna; conteúdo alinhado ao bottom em mobile quando a etapa pedir (B63 tipifica), top em
md+— o shell expõe um prop de alinhamento (contentAlign: 'start' | 'end') sem hardcodar o grid de sinais. - Rotas sob
/campanha/…(recomendação UX-1 travada: rota, não Sheet — sobrevive a refresh/Zap). Este item define o layout e o header; as rotas concretas nascem com B60/B61/B63 ou com o wiring A1. - Sem migration / Consent / action de escrita.
- a11y: seta com nome acessível contendo o verbo visível (“Voltar”); caption do município anunciável (
aria-nomeada, não só cor); foco gerenciado ao trocar de passo (mínimo: foco no heading do passo).
Decisões travadas
- Voltar mobile = chrome do wizard; desktop = browser. Rejeitado: seta também em
md+(ruído; duplica gesto nativo); sóLink“Cancelar” no rodapé (longe do polegar no topo). - Wizard = rota, não Drawer sobre o Início. Rejeitado: Sheet full-screen (refresh/Zap perde contexto; URL não compartilha passo). Fonte: gate UX-1 2026-07-29 + rascunho.
- Shell não grava estado de domínio — só navegação/chrome. Estado de município/votos/sinal fica nas etapas ou num store mínimo do fluxo (Adiado com gatilho se 3º wizard pedir). O shell recebe o rótulo (
municipalityLabel?: string/ slot) — não resolve slug→nome sozinho. - Município no topo = caption sticky semi-discreta, centro superior. Rejeitado: A) só no título do passo (some da hierarquia quando a pergunta muda); B) chip/badge primário gritante (compete com CTA); C) link obrigatório para
/municipios/[slug]na v1 (abandona o wizard no meio do ritual — Adiado com gatilho). Fonte: produto 2026-07-29. - i18n: ids em inglês (
WizardShell,onBack,trailingAction,municipalityLabel/contextCaption); copy pt-BR.
Questões em aberto
- Path canônico (
/campanha/acoes/…vs/campanha/wizards/…)? Opções: Aacoes| Bwizards. Recomendação: A — linguagem de mesa (“ação”), alinhada ao catálogo B45. (assumido — validar no 1º wizard que montar rota)
Abordagem proposta
flowchart TB
Home["/campanha Início"] --> Route["rota de passo"]
Route --> Shell["CampaignWizardShell"]
Shell --> Back["Voltar md:hidden"]
Shell --> Ctx["municipalityLabel sticky centro"]
Shell --> Trail["trailingAction?"]
Shell --> Body["children = etapa"]
Componentes:
CampaignWizardShellemsrc/components/campaign/shared/(oushell/): header (back | caption município | trailing) + main; reusa tokens/Buttonghost para a seta (ArrowLeft); caption comsticky top-0no scroller do passo (respeitar safe-area / não cobrir a seta).- Hook/helper de passo (opcional neste item):
useWizardBack({ previousHref })— se <2 call sites, inline no shell. - Migration: Sem migration, sem collection, sem server action.
Dependências
- Dura: B45 ✓ (ações existem; wizards ainda inertes). Soft: B46 ✓ (thumb zone — alinhamento de conteúdo).
- Desbloqueia B60, B61, B63.
Não escopo
- Busca de município → B60 (é quem escolhe; o caption só aparece depois). Ajuste de votos → B61. Modelo/UI de sinal → B62/ B63.
- Resumo final + CTA “Registrar atualização” do A1 → wiring do wizard A1 (após estas etapas).
- Rascunho persistente cross-session → Adiado.
- Modo focado da busca do Início no focus → B66.
Rabbit holes
- State machine genérica de wizard (XState / multi-route orchestrator). Mitigação: shell burro + hrefs/query de passo por fluxo; extrair só no 3º wizard.
- App chrome (
CampaignSidebar) dentro do wizard. Mitigação: layout de(app)pode permanecer; visualmente o conteúdo é full-bleed de passo — não inventar segundo layout group sem medição. - Segundo header sticky competindo com
CampaignPageShell. Mitigação: um único sticky no chrome do wizard; não sticky o título do passo também.
Adiado com gatilho
- Persistência de rascunho (município + ação) ao reabrir. Revisitar quando a 1ª sessão real for interrompida por Zap e o CG reclamar (UX-1 § contrato #8).
- Caption do município como
Linkpara a ficha. Revisitar se a mesa pedir “abrir município” sem sair do fluxo (ex. Sheet de leitura) — v1 é texto só. - Split server shell + client focus island. Revisitar em B60 se o passo de busca não precisar hidratar o header inteiro (
CampaignWizardShellhoje é'use client'por umfocus()noh1). backdrop-blurno header sticky. Revisitar se scroll longo em B60+ mostrar custo de paint; v1 mantém blur alinhado ao app chrome.
Já resolvido no simplify (não reabrir)
- Cinco
page.tsxduplicados → rota dinâmicaacoes/[slug]/page.tsx+WizardMunicipalityPlaceholderStep. CAMPAIGN_HOMEemcampaignPaths.ts;WIZARD_MUNICIPALITY_STEP_TITLEemcampaignWizardCopy.ts.aria-labelledbyno<main>;data-slot="wizard-mobile-back"nos testes.
Referências
- fluxos-acao-primeiro-inicio.md · catalogo-acoes-inicio-por-persona.md ·
CampaignPageShell·PRODUCT.mdClarity under pressure - AGENTS.md — naming; campanha auth; sem Consent novo
PRODUCT.md/DESIGN.md— Field Desk / tokens campaign