mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
docs: registrar planos B59–B63 dos wizards UX-1 no Início.
Detalha chassis, busca de município, ajuste de votos, simplificação do sinal e fluxo de registro; atualiza o rascunho de fluxos e o roadmap.
This commit is contained in:
parent
a75fe2c88f
commit
72084f1e56
7 changed files with 559 additions and 35 deletions
109
docs/plans/ajuste-votos-wizard.md
Normal file
109
docs/plans/ajuste-votos-wizard.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# Etapa de ajuste de votos no wizard (cenários + atalhos)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-29
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B61 — UX-1 wizards)
|
||||
Impeccable: C — UI nova de passo numérico com coerência entre cenários
|
||||
Appetite: ~1–1,25 dia eng; 3 sub-passos de cenário + atalhos + navegação de violação; grava via action existente no wiring A1 (neste item: UI + validação client espelhando servidor)
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Feel the action; Clarity under pressure) / `DESIGN.md` · `VoteEstimateScenarioInputs` / `MunicipalityListExpectedVotesControl` · tema `campaign`.
|
||||
|
||||
Na implementação: shape → craft → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona:** CG no celular ajustando Cairu −100 / Salinas 300→150; quer atalho, não teclado minucioso.
|
||||
- **Job principal:** editar **um cenário por vez** (média → pessimista → otimista), com atalhos e confirmação explícita para o próximo.
|
||||
- **Estratégia de cor:** Restrained; warning de coerência usa tom de alerta do tema (não Signal Red decorativo).
|
||||
- **Edit where you see:** não neste passo — é fluxo com **submit explícito** por cenário (“Ajustar estimativa pessimista →”), exceção já prevista em `campanha-edit-where-you-see` para confirmação.
|
||||
- **Anti-goals:** três inputs na mesma tela com um “Salvar”; input enorme estilo hero-metric SaaS; auto-save silencioso sem CTA.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim — valor atual de `expectedVotes[cenário]` e o valor em edição.
|
||||
- **Decisões:** staff escolhe o novo número da projeção da mesa por cenário; decide se corrige coerência ou volta ao cenário que quebrou.
|
||||
- **Forma:** **número + contexto** (rótulo do cenário + valor atual). **Rejeitado:** chart; três gauges; % estadual.
|
||||
- **Profile:** 1 inteiro por passo; ordem `pessimistic ≤ central ≤ optimistic` (`getVoteEstimateOrderViolation`).
|
||||
- **Anti-goals de dado:** não misturar `declaredVotes` de liderança; liderança nunca vê esta etapa.
|
||||
|
||||
## Contexto
|
||||
|
||||
Ação A1 do Início ([fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md)): após escolher município (**B60**), ajustar estimativas. Pedido (2026-07-29):
|
||||
|
||||
- Input **já preenchido** com o valor atual; editável.
|
||||
- Atalhos: **2×**, **+50**, **−50**, **+100**, **−100** (aplicam sobre o valor corrente do input).
|
||||
- Input **grande, não enorme**.
|
||||
- Ordem fixa: **média → pessimista → otimista** (`central` → `pessimistic` → `optimistic`).
|
||||
- Se um cenário **quebrar a lógica** com um já definido, navegar **para o cenário que ficou inconsistente**, mostrar **warning** (por quê) e atalho para **voltar ao cenário que a pessoa acabou de editar**.
|
||||
- CTA por passo: ex. **“Ajustar estimativa pessimista →”** (não auto-avanço mudo).
|
||||
|
||||
Invariante de servidor já existe: `getVoteEstimateOrderViolation` + hook em `Municipality` / `VOTE_ESTIMATE_ORDER_ERROR_MESSAGE` (`src/lib/voteEstimate.ts`).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Etapa(s) `WizardExpectedVotesStep` (ou uma rota com `?scenario=`) no **B59**.
|
||||
- Estado local dos três números; ao confirmar um cenário, validar contra os já confirmados; se violar, `setScenario(violation)` + banner + link “Voltar para [cenário editado]”.
|
||||
- Atalhos mutam o draft (floor 0, ceiling `MAX_VOTE_COUNT`); `2×` = dobrar o valor atual do input (se vazio/0, definir comportamento: ficar 0 ou no-op — **recomendação:** no-op se <1).
|
||||
- CTA confirma o cenário e avança (ou conclui o bloco de votos e devolve ao orquestrador A1).
|
||||
- Reusar labels `voteEstimateScenarioLabels` (Média / Pessimista / Otimista).
|
||||
- Escrita persistente: **reusar** action/endpoint de `expectedVotes` já usados pela lista (`MunicipalityListExpectedVotesControl` / POST expected-votes) — neste item pode entregar UI + schema de passo; o commit final do A1 pode batchar os três no wiring. **Mínimo:** não inventar segunda semântica de ordem.
|
||||
- Staff-only; sem migration / Consent.
|
||||
- Unit: coerência (casos que forçam jump + atalho de retorno); atalhos aritméticos.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Ordem de edição: média → pessimista → otimista** (pedido). **Rejeitado:** três campos numa tela; ordem pessimista-primeiro (é a ordem do invariante de armazenamento, não do ritual de mesa).
|
||||
- **Confirm explícito por cenário** (CTA nomeado). **Rejeitado:** auto-save estilo B27 (pedido pede botão; e coerência cross-cenário pede commit consciente).
|
||||
- **Violação → navegar ao cenário quebrado + warning + atalho de retorno.** **Rejeitado:** só toast bloqueando; forçar edição inline sem mudar de passo.
|
||||
- **Atalhos sobre o valor do input**, não sobre o valor persistido antigo após dirty. **Rejeitado:** resetar ao “atual” a cada atalho.
|
||||
- **i18n:** `central`/`pessimistic`/`optimistic` nos ids; copy “Ajustar estimativa … →”.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Gravar no servidor a cada CTA de cenário ou só no commit final do A1?** **Opções:** A por cenário | B batch no fim. **Recomendação:** B no wiring A1 (um write); este item valida localmente e devolve os três valores — evita half-write se o CG abandonar no pessimista. _(assumido — validar na implementação do A1)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Mun["município B60"] --> C["passo Média"]
|
||||
C --> P["passo Pessimista"]
|
||||
P --> O["passo Otimista"]
|
||||
C & P & O --> V{"ordem ok?"}
|
||||
V -->|não| Jump["vai ao cenário violado + warning"]
|
||||
V -->|sim| Next["próximo bloco / quer também?"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`WizardExpectedVotesStep`**: input grande (`text-2xl`/`text-3xl` — medir no craft; não `text-6xl`), fila de atalhos (`Button` outline), CTA, banner de coerência.
|
||||
- **Helper puro** `applyVoteShortcut(value, 'double' | '+50' | …)` + reuso `getVoteEstimateOrderViolation`.
|
||||
- **Migration:** Sem migration (campos já existem).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: **B59**. Soft: **B60** (município já escolhido). Reuso: A10 ✓ / `voteEstimate.ts` / controle da lista.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Wiring completo A1 (sinal/tendência/resumo) → fatia posterior UX-1.
|
||||
- Edição de `votePledge.estimatedVotes` → fora (esta etapa = `municipality.expectedVotes` da mesa).
|
||||
- Leader → lockdown.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Mini-spreadsheet dos 3 cenários com sync bidirecional.** **Mitigação:** um cenário visível por vez.
|
||||
- **Recalcular meta E8 / cobertura ao vivo no passo.** **Mitigação:** número cru; cobertura fica no resumo A1 ou no Quadro.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Preview de cobertura E8 no rodapé do passo.** Revisitar se o CG pedir “isso fecha a meta?” durante o ajuste (sessão).
|
||||
|
||||
## Referências
|
||||
|
||||
- [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) · [cenarios-estimativa-votos.md](cenarios-estimativa-votos.md) · `src/lib/voteEstimate.ts` · `MunicipalityListExpectedVotesControl.tsx` · `VoteEstimateScenarioInputs.tsx` · collection `Municipality` hook de ordem
|
||||
- AGENTS.md — assimetria leader; `overrideAccess: false`
|
||||
- `PRODUCT.md` / `DESIGN.md` · `.cursor/rules/campanha-edit-where-you-see.mdc` (exceção submit explícito)
|
||||
96
docs/plans/busca-municipio-wizard.md
Normal file
96
docs/plans/busca-municipio-wizard.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Etapa de busca de município nos wizards
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-29
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B60 — UX-1 wizards)
|
||||
Impeccable: B — encaixe da UX da busca do Início (B47/B48) numa etapa de wizard, só municípios
|
||||
Appetite: ~1 dia eng; input + lista de hits + auto-avanço; soft reuso do loader B48
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` / `DESIGN.md` · B47 `CampaignHomeSearch` / B48 linhas de município · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona:** CG digita “Cairu” no meio do ritual A1/A2 e quer o município em 1 toque.
|
||||
- **Job principal:** achar o município no escopo do ator e **avançar sozinho** ao selecionar.
|
||||
- **Estratégia de cor:** Restrained.
|
||||
- **Edit where you see:** não — seleção é navegação de fluxo.
|
||||
- **Anti-goals:** grupos (TI/lideranças/…); botão “Continuar” após selecionar; card por linha.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim — hits de município (nome, TI, opcional “2022” se B48 já tiver o readout).
|
||||
- **Decisões:** escolher **qual** município entra no fluxo.
|
||||
- **Forma:** lista ranqueada por nome (word-start), igual espírito B48. **Rejeitado:** mapa; grupos multi-entidade.
|
||||
- **Profile:** scoped ao access; tipicamente <20 hits.
|
||||
- **Anti-goals de dado:** sem inventar métrica; sem TIs neste passo (wizard precisa de unidade operacional `municipality`).
|
||||
|
||||
## Contexto
|
||||
|
||||
No Início, **B47** ✓ entrega input + modo focado; **B48** lista Municípios (+ TIs no mesmo grupo). Nos wizards (pedido 2026-07-29): **mesma experiência de busca**, com duas diferenças — (1) **sem grupos**: só municípios; (2) **selecionar = avançar** ao próximo passo, sem confirmar.
|
||||
|
||||
Serve A1 (votos), A2 (sinal), A3/A4/A5 e qualquer fluxo que precise de local cedo ([fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) § contrato #2).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Etapa reutilizável (ex. `WizardMunicipalitySearchStep`) montada no **B59**: input debounce (mesmo espírito 250 ms de B47), lista de hits **apenas** `municipality`, escopo `overrideAccess: false`.
|
||||
- Clique/Enter no hit → callback/`router.push` para o próximo passo **com município escolhido** (slug ou id na URL/query do fluxo).
|
||||
- Sem botão “Continuar” / “Confirmar município”.
|
||||
- Recentres / prioritários opcionais acima da busca (soft; cortável) — 3 recentes se `recentVisits` já existir no Início.
|
||||
- Match: `matchesAtWordStart` / `normalizeSearchPhrase` (`lib/wordStartFilter.ts`).
|
||||
- Sem migration / Consent.
|
||||
- Se **B48** já tiver `searchHomeMunicipalities`, **filtrar/omitir TIs** e reusar o loader; senão, loader mínimo só de municípios (depth: não duplicar fuzzy).
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Só municípios** — TIs fora deste passo (wizard grava em `municipality`). **Rejeitado:** copiar B48 com TIs (não há `municipality` para um TI).
|
||||
- **Select = auto-avanço.** **Rejeitado:** seleção + “Continuar” (pedido explícito; atrito no ritual).
|
||||
- **Mesma linguagem visual da busca do Início** (linha sem card, tipografia). **Rejeitado:** `Command`/`Combobox` de lista (B27) como UI primária — é outro modelo mental.
|
||||
- **i18n:** `WizardMunicipalitySearch`, copy “Em qual município?”.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Mostrar votos 2022 à direita no wizard?** **Opções:** A sim (paridade B48) | B só nome+TI. **Recomendação:** B no v1 se B48 ainda não landou; A quando o readout existir e não atrasar o appetite. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Shell["B59 Shell"] --> Input["input debounce"]
|
||||
Input --> Loader["searchMunicipalitiesScoped"]
|
||||
Loader --> Hits["lista só município"]
|
||||
Hits -->|select| Next["próximo passo + slug"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`WizardMunicipalitySearchStep`** (client island): input + results; registra seleção.
|
||||
- **Loader** — reuso B48 ou `utilities/municipality/` com `overrideAccess: false`.
|
||||
- Reuso: `useHomeSearchQuery` pattern / debounce B47; **não** importar o chrome focado do Início inteiro.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: **B59**. Soft: **B48** (UX/loader), **B47** ✓ (debounce/modo focado como referência).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Grupos lideranças/assessores/… → B49–B53 (só Início). Ajuste de votos → **B61**. Chassis → **B59**.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Um único componente de busca para Início e wizard.** **Mitigação:** compartilhar loader + row renderer; chrome (grupos vs só município, onSelect vs Link) fica nos wrappers — extrair shared só com 2 call sites reais medidos.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Chips de recentes/prioritários acima do input.** Revisitar se o CG pedir atalho sem digitar (sessão).
|
||||
|
||||
## Referências
|
||||
|
||||
- [busca-global-inicio-input.md](busca-global-inicio-input.md) · [busca-global-resultados-municipios.md](busca-global-resultados-municipios.md) · [chassis-wizard-campanha.md](chassis-wizard-campanha.md) · `lib/wordStartFilter.ts` · `recentVisits` utilities
|
||||
- AGENTS.md — access advisor; `overrideAccess: false`
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
95
docs/plans/chassis-wizard-campanha.md
Normal file
95
docs/plans/chassis-wizard-campanha.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Chassis do wizard de campanha (shell + voltar)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-29
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B59 — UX-1 wizards)
|
||||
Impeccable: C — UI nova (shell de fluxo multi-passo sob `/campanha`)
|
||||
Appetite: ~0,75 dia eng; layout de passo + header mobile + slot de conteúdo; 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.
|
||||
- **Job principal:** enquadrar qualquer etapa de wizard (busca, votos, sinal…) com volta óbvia no mobile e conteúdo focável.
|
||||
- **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.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — chrome de navegação; conteúdo numérico/textual nas etapas B60–B63.
|
||||
|
||||
## Contexto
|
||||
|
||||
Chassis UX-1 do Início (**B43–B47** ✓) deixa ações inertes até existir **wizard**. O rascunho [fluxos-acao-primeiro-inicio.md](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, slot `children`, slot opcional de ação no topo direito (ex. “Pular…” do B63), **back control só em viewport mobile** (`md:hidden` ou equivalente) que navega ao passo anterior / Início conforme o fluxo.
|
||||
- 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”); 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).
|
||||
- **i18n:** ids em inglês (`WizardShell`, `onBack`, `trailingAction`); copy pt-BR.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Path canônico (`/campanha/acoes/…` vs `/campanha/wizards/…`)?** **Opções:** A `acoes` | B `wizards`. **Recomendação:** A — linguagem de mesa (“ação”), alinhada ao catálogo B45. _(assumido — validar no 1º wizard que montar rota)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Home["/campanha Início"] --> Route["rota de passo"]
|
||||
Route --> Shell["CampaignWizardShell"]
|
||||
Shell --> Back["Voltar md:hidden"]
|
||||
Shell --> Trail["trailingAction?"]
|
||||
Shell --> Body["children = etapa"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`CampaignWizardShell`** em `src/components/campaign/shared/` (ou `shell/`): header + main; reusa tokens/`Button` ghost para a seta (`ArrowLeft`).
|
||||
- **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**. 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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).
|
||||
|
||||
## Referências
|
||||
|
||||
- [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) · [catalogo-acoes-inicio-por-persona.md](catalogo-acoes-inicio-por-persona.md) · `CampaignPageShell` · `PRODUCT.md` Clarity under pressure
|
||||
- AGENTS.md — naming; campanha auth; sem Consent novo
|
||||
- `PRODUCT.md` / `DESIGN.md` — Field Desk / tokens campaign
|
||||
|
|
@ -80,16 +80,16 @@ Copy: zero “broker”, “campo sem captura”, “LQ” nos passos. Se precis
|
|||
|
||||
**Abre:** botão Início · ou sugestão E11 “atualizar projeção” · ou deep-link.
|
||||
|
||||
| Passo | Pergunta | UI | Escrita (já existe / quase) |
|
||||
| ----- | ----------------------------------- | ---------------------------------------------- | ------------------------------------------------ |
|
||||
| 1 | Em qual município? | Busca + 3 recentes / prioritários | — |
|
||||
| 2 | Qual a nova estimativa **média**? | Número; mostra valor atual | `expectedVotes.central` |
|
||||
| 3 | E o **pessimista**? | Número; default = atual ou coerência com média | `expectedVotes.pessimistic` |
|
||||
| 4 | E o **otimista**? | Número | `expectedVotes.optimistic` |
|
||||
| 5 | Quer registrar **o que aconteceu**? | Sim → mini A2 embutido / Não | `municipalityUpdate` sinal |
|
||||
| 6 | Quer mudar a **tendência**? | Sim → A3 embutido / Não | `politicalTrend` + nota |
|
||||
| 7 | Quer ajustar **liderança** agora? | Sim → A4 embutido (curto) / Não | leadership / pledge |
|
||||
| 8 | Resumo → **Registrar atualização** | CTA único | transação ou sequência das actions já existentes |
|
||||
| Passo | Pergunta | UI | Escrita (já existe / quase) |
|
||||
| ----- | ----------------------------------- | ------------------------------------------------------------------------------- | --------------------------- |
|
||||
| 1 | Em qual município? | Busca só municípios (UX Início, sem grupos); select → avança (**B60**) | — |
|
||||
| 2 | Qual a nova estimativa **média**? | Input grande pré-preenchido + atalhos 2× / ±50 / ±100; CTA “Ajustar…” (**B61**) | `expectedVotes.central` |
|
||||
| 3 | E o **pessimista**? | Idem; se quebrar ordem vs média → jump + warning + atalho de volta (**B61**) | `expectedVotes.pessimistic` |
|
||||
| 4 | E o **otimista**? | Idem + coerência vs anteriores (**B61**) | `expectedVotes.optimistic` |
|
||||
| 5 | Quer registrar **o que aconteceu**? | Sim → mini A2 (**B63**) / Não | `municipalityUpdate` sinal |
|
||||
| 6 | Quer mudar a **tendência**? | Sim → A3 embutido / Não | `politicalTrend` + nota |
|
||||
| 7 | Quer ajustar **liderança** agora? | Sim → A4 embutido (curto) / Não | leadership / pledge |
|
||||
| 8 | Resumo → **Registrar atualização** | CTA único | actions existentes |
|
||||
|
||||
**Corte 03/08 se apertar:** passos 1–4 + 8; 5–7 como “Quer também?” com um campo cada, sem sub-wizard profundo.
|
||||
|
||||
|
|
@ -99,17 +99,17 @@ Copy: zero “broker”, “campo sem captura”, “LQ” nos passos. Se precis
|
|||
|
||||
## Fluxo A2 — Registrar o que aconteceu (sinal)
|
||||
|
||||
| Passo | Pergunta | UI |
|
||||
| ----- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | Em qual município? | Busca (pular se veio de A1) |
|
||||
| 2 | O que aconteceu? (título curto) | Texto — “Vereador foi apoiar outro candidato” |
|
||||
| 3 | Que tipo? | Lista curta em linguagem de mesa: Invasão · Perda de apoio · Novo apoio · Dificuldade · Outro (mapear para `kind`/enum existente) |
|
||||
| 4 | Quem contou? (fonte) | Texto livre |
|
||||
| 5 | Confirmou com outra pessoa? | Sim / Não (triangulação) |
|
||||
| 6 | Urgente? | Sim / Não |
|
||||
| 7 | Resumo → **Registrar** | |
|
||||
**Modelo (B62, 2026-07-29):** só `signalType` + texto (`body`). **Sem** fonte nem triangulado. Rótulos de mesa (enum values estáveis): Invasão · Rede esfriou · Adversário apareceu · Alguém pediu algo · Outro.
|
||||
|
||||
Opcional ao fim: “Isso muda a projeção de votos?” → entra em A1 passo 2.
|
||||
| Passo | Pergunta | UI |
|
||||
| ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | Em qual município? | Busca só municípios, select → avança (**B60**; pular se veio de A1) |
|
||||
| 2 | Que tipo? | Grid de tiles grandes + Drawer de info por tipo (**B63**); “Pular registro de sinal” no topo direito **só se** a origem ≠ ação `register-signal` |
|
||||
| 3 | Detalhe? | Texto livre; Skip (mesma regra); **Salvar** inferior direito (**B63**) |
|
||||
|
||||
Chrome de toda etapa: seta **Voltar** no topo esquerdo **só mobile**; desktop = browser (**B59**).
|
||||
|
||||
Opcional ao fim: “Isso muda a projeção de votos?” → entra em A1 / **B61**.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -214,16 +214,17 @@ Não competir com os botões.
|
|||
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)
|
||||
### Wizards (etapas compartilhadas registradas 2026-07-29)
|
||||
|
||||
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
|
||||
3. **B59** Chassis (shell + voltar mobile) — [chassis-wizard-campanha.md](chassis-wizard-campanha.md)
|
||||
4. **B60** Busca município (só municípios; select → avança) — [busca-municipio-wizard.md](busca-municipio-wizard.md)
|
||||
5. **B61** Ajuste votos (média→pessimista→otimista + atalhos + coerência) — [ajuste-votos-wizard.md](ajuste-votos-wizard.md)
|
||||
6. **B62** Simplificar modelo de sinal (tipo+texto; labels mesa) — [simplificar-modelo-sinal.md](simplificar-modelo-sinal.md)
|
||||
7. **B63** Wizard sinal (grid tipo + texto + pular) — [wizard-registro-sinal.md](wizard-registro-sinal.md)
|
||||
8. **A1** wiring (orquestrar B60+B61 + “quer também?” sinal/tendência + resumo/CTA)
|
||||
9. **A6** — atalho já em B45 (`uncovered-municipalities`)
|
||||
10. **A2** standalone = B60+B63 (se não ficou bom só embutido em A1)
|
||||
11. **A4** curto · **A5** + edição de demanda · E11 → wizards · A3 standalone
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
90
docs/plans/simplificar-modelo-sinal.md
Normal file
90
docs/plans/simplificar-modelo-sinal.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Simplificar modelo de sinal (tipo + texto; sem fonte/triangulado)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-29
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B62 — UX-1 / C12 sinais)
|
||||
Impeccable: B — schema + forms existentes (`MunicipalitySignalFields`, B26, admin); UI nova de escolha = **B63**
|
||||
Appetite: ~0,75–1 dia eng; migration drop + zod/collection/UI lista/detail; rename de labels; ajuste E11
|
||||
Responsável: —
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A neste item como superfície nova — remove campos; labels novas alimentam **B63** e B26.
|
||||
|
||||
## Contexto
|
||||
|
||||
C12 ✓ tipou `municipalityUpdate` `kind: 'sinal'` com `signalType`, `signalSource` (obrigatório) e `triangulated`. B26 ✓ e `MunicipalitySignalFields` expõem os três. Pedido de produto (2026-07-29, gate confirmado): **sinais = só tipo + texto**; dropar Fonte e Triangulado; **renomear rótulos** para militantes PT ~60 anos (sem “broker”).
|
||||
|
||||
E11 ✓ lê `triangulated` em sinais de adversário (`municipalityTriggers.ts` / `suggestionCatalog`) — ao dropar, o motor trata presença do sinal como o ramo “fonte única” (ou só “há sinal”), sem segunda dimensão.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Migration: remover colunas `signal_source` e `triangulated` de `municipality_update` (e enums/admin conforme snapshot Payload); `pnpm migrate:create simplify_municipality_signal_fields`.
|
||||
- Zod `municipalityUpdateCreateSchema`: para `kind: 'sinal'`, exigir `signalType` + `body`; **não** exigir `signalSource`; remover `triangulated`.
|
||||
- Collection `MunicipalityUpdate`: dropar fields; ajustar `beforeValidate` que hoje exige fonte.
|
||||
- Labels/descriptions (enum **values** preservados — sem rewrite de rows):
|
||||
|
||||
| value | label | description curta |
|
||||
| ------------------- | ------------------- | ----------------------------------------- |
|
||||
| `invasao` | Invasão | Adversário ocupando nosso espaço |
|
||||
| `esfriamento` | Rede esfriou | Aliados pararam de responder / cairam |
|
||||
| `visita_adversario` | Adversário apareceu | Visita ou agenda dele no município |
|
||||
| `proposta_broker` | Alguém pediu algo | Liderança/intermediário pediu ou ofereceu |
|
||||
| `outro` | Outro | Fato importante que não encaixa acima |
|
||||
|
||||
- Atualizar `MunicipalitySignalFields`, B26 form, detail/dossier readouts, int tests, seeds se houver.
|
||||
- E11: remover branch `triangulated` do input de sugestão; copy do catálogo que cita “triangulado” / “fonte única” no eixo de adversário → presença do sinal basta (recalibrar texto mínimo; **não** reabrir E15).
|
||||
- Sem Consent novo. Sem UI de grid do wizard (isso é **B63**).
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Modelo = `signalType` + `body` apenas.** **Rejeitado:** manter fonte/triangulado opcionais “por se acaso” (pedido: simplificar; campos mortos confundem mesa).
|
||||
- **Enum values estáveis; só labels/descriptions mudam.** **Rejeitado:** renomear `proposta_broker` → `proposta_intermediario` neste slice (migration de dados + grep em E11/tests sem ganho de UX). Gatilho: se copy interna/código ainda vazar “broker” em string de usuário após labels — aí rename.
|
||||
- **E11 sem dimensão triangulado.** **Rejeitado:** inventar proxy (“texto longo = triangulado”); manter coluna só para o motor.
|
||||
- **i18n:** ids `invasao`…; copy pt-BR nova.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Textos longos do drawer de info (B63) — quem redige?** **Opções:** A eng com copy provisória no plano B63 | B assessoria. **Recomendação:** A no ship (o quê / consequências / quando usar em 3–5 frases); B revisa no R6. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Mig["migration DROP fonte/triangulated"] --> Zod["zod + collection"]
|
||||
Zod --> UI["SignalFields + B26"]
|
||||
Zod --> E11["triggers/catalog"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- Migration + `MunicipalityUpdate.ts` + `municipalityUpdate.ts` schema.
|
||||
- `MunicipalitySignalFields.tsx` — select de tipo + textarea; sem checkbox/fonte.
|
||||
- `municipalityTriggers.ts` / `suggestionCatalog.ts` — dropar `triangulated` do tipo de input.
|
||||
- View models de update/dossier — parar de exibir fonte/triangulado.
|
||||
- **Migration:** sim (drop columns; sem backfill necessário além de drop).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: **C12** ✓. Soft: B26 ✓ (superfície a atualizar). Desbloqueia **B63**.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Grid de botões + drawer de info + “Pular” → **B63**.
|
||||
- Novos tipos de sinal (Perda de apoio / Novo apoio do rascunho UX-1) — **não** expandir taxonomia neste item; só renomear os 5 existentes. Gatilho: sessão pedir tipo que não cabe em `outro`.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Reescrever taxonomia inteira + migração de valores históricos.** **Mitigação:** labels only.
|
||||
- **Unificar `kind` urgente/nota com sinal.** **Mitigação:** fora — C12 já separou.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Rename enum `proposta_broker`.** Revisitar se `broker` ainda aparecer em UI/admin após labels (grep pt-BR + admin).
|
||||
- **Novos signalType values.** Revisitar com evidência de mesa (tipos que caem sempre em `outro`).
|
||||
|
||||
## Referências
|
||||
|
||||
- [registro-fundacao.md](registro-fundacao.md) · [registrar-sinal-lista-municipios.md](registrar-sinal-lista-municipios.md) · [motor-de-sugestoes.md](motor-de-sugestoes.md) · `src/lib/schemas/municipalityUpdate.ts` · `src/collections/MunicipalityUpdate.ts` · `MunicipalitySignalFields.tsx` · `municipalityTriggers.ts`
|
||||
- AGENTS.md — migrations; `overrideAccess: false`
|
||||
- Impeccable: B — craft compacto nos forms existentes
|
||||
113
docs/plans/wizard-registro-sinal.md
Normal file
113
docs/plans/wizard-registro-sinal.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Wizard registrar sinal — escolher tipo + detalhar texto
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-07-29
|
||||
Item do roadmap: [docs/roadmap.md](../roadmap.md) (Trilha B, item B63 — UX-1 wizards)
|
||||
Impeccable: C — UI nova (grid de tipos + drawer de info + passo de texto)
|
||||
Appetite: ~1–1,25 dia eng; 2 etapas no B59; write via action `createMunicipalityUpdate` existente
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` / `DESIGN.md` · `Drawer` shadcn · tema `campaign` · labels de **B62**.
|
||||
|
||||
Na implementação: shape → craft → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona:** CG/assessor no polegar; acabou de ouvir no Zap e precisa classificar o fato sem jargão.
|
||||
- **Job principal:** escolher o tipo de sinal em 1 toque (com info sob demanda) e, se quiser, detalhar em texto; ou pular quando o sinal não era o motivo do wizard.
|
||||
- **Estratégia de cor:** Restrained — fundo transparente, borda cinza clara (`border-border`), título/ícone `foreground`; sem fill primary nos tiles.
|
||||
- **Edit where you see:** não — fluxo com Salvar explícito no passo de texto.
|
||||
- **Anti-goals:** select nativo escondido; “broker”; obrigar sinal quando o fluxo veio de votos; cards com sombra/elevação.
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A como métrica — tipo categórico + texto livre. Decisão = classificar o fato e se registrar agora ou pular.
|
||||
|
||||
## Contexto
|
||||
|
||||
Após **B62**, o modelo é `signalType` + `body`. Pedido (2026-07-29, gate confirmado):
|
||||
|
||||
**Etapa 1 — tipo**
|
||||
|
||||
- Cada tipo = botão **quadrado grande**, cantos arredondados; ícone canto **superior esquerdo**; título + descrição curta alinhados ao **inferior esquerdo**; fundo transparente + borda cinza clara; título/ícone pretos (`foreground`).
|
||||
- Ícone de **informação** no tile → bottom **Drawer** com: o que é, consequências, quando usar (copy longa; provisória ok).
|
||||
- Grid; alinhamento do bloco: **bottom no mobile**, **top em tablet/desktop** (prop `contentAlign` do **B59**).
|
||||
- Topo direito: **“Pular registro de sinal”** — **exceto** quando a ação que abriu o wizard foi `register-signal` (B45).
|
||||
|
||||
**Etapa 2 — texto**
|
||||
|
||||
- Texto livre para detalhar.
|
||||
- “Pular registro de sinal” ainda no topo direito (mesma regra de origem).
|
||||
- Inferior direito: **“Salvar”** → cria `municipalityUpdate` `kind: 'sinal'`.
|
||||
|
||||
Município: já escolhido (**B60**) ou pré-preenchido se veio embutido de A1.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Duas telas/passos no shell B59: `WizardSignalTypeStep` + `WizardSignalBodyStep`.
|
||||
- Catálogo de tiles: mapear os 5 `municipalitySignalTypes` → ícone Lucide + label/description curta (B62) + `infoContent` (markdown/plain).
|
||||
- Info: botão `Info` com `stopPropagation` para não selecionar o tipo ao abrir o Drawer.
|
||||
- Select de tipo = avança para o passo de texto (sem CTA intermediário).
|
||||
- Skip: `Link`/button → próximo bloco do fluxo pai ou Início; **hidden** se `entryAction === 'register-signal'`.
|
||||
- Salvar: `createMunicipalityUpdate` / formAction existente com `overrideAccess: false`; pending no botão (Feel the action).
|
||||
- Atualizar B26 list control **não** é obrigatório a adotar o grid (pode permanecer select pós-B62); opcional Adiado.
|
||||
- Sem migration (B62 já migrou). Sem Consent.
|
||||
- Unit/e2e smoke: skip oculto no entry signal; info não seleciona tipo; salvar exige tipo+body.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Duas etapas (tipo → texto), um item de roadmap.** **Rejeitado:** IDs separados (texto é fino demais sozinho).
|
||||
- **Skip visível só se a origem ≠ `register-signal`.** **Rejeitado:** sempre mostrar (permitiria “registrar sinal” sem sinal); nunca mostrar (bloqueia “quer também?” após votos).
|
||||
- **Tiles transparentes + borda**, não filled primary. **Rejeitado:** cards elevados / Signal Red no idle.
|
||||
- **Info = Drawer bottom** (pedido). **Rejeitado:** Tooltip only (touch); Dialog centrado.
|
||||
- **i18n:** `signalType`, `entryAction`, `skipSignal`; copy “Pular registro de sinal” / “Salvar”.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Após Salvar no fluxo standalone A2: ir ao Início ou oferecer “ajustar votos”?** **Opções:** A Início + toast | B “Quer atualizar votos?”. **Recomendação:** A no v1; B no wiring A1/A2 contínuo. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Entry["origem: register-signal | embutido"] --> Type["grid tipos"]
|
||||
Type -->|info| Drawer["Drawer detalhes"]
|
||||
Type -->|select| Body["textarea"]
|
||||
Type & Body -->|skip se permitido| Skip["próximo / Início"]
|
||||
Body -->|Salvar| Action["createMunicipalityUpdate"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`WizardSignalTypeStep`** + **`WizardSignalBodyStep`** em `components/campaign/municipality/` (ou `shared/` se 2º call site).
|
||||
- **`municipalitySignalTypeMeta`** em `lib/` (ícone id, short description, long info) — client-safe; labels de B62.
|
||||
- Reuso: `Drawer`, `Button`, B59 trailing slot + `contentAlign="end"` no mobile da etapa de tipo.
|
||||
- **Migration:** Sem migration (depende de B62).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: **B59**, **B62**. Soft: **B60** (município no standalone). Soft: B45 ✓ (`register-signal` id).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Dropar fonte/triangulado / labels → **B62** (pré-requisito).
|
||||
- Wiring “quer também?” dentro de A1 → fatia A1.
|
||||
- Trocar B26 para o grid de tiles → Adiado.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **CMS dos textos de info.** **Mitigação:** constantes em `lib/` até R6/assessoria.
|
||||
- **Mais de 5 tipos / multi-select.** **Mitigação:** um tipo por sinal (C12).
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Grid de tipos no Popover B26.** Revisitar se o select pós-B62 ainda gerar dúvida de classificação em campo.
|
||||
- **Copy longa revisada pela assessoria.** Revisitar no R6.
|
||||
|
||||
## Referências
|
||||
|
||||
- [simplificar-modelo-sinal.md](simplificar-modelo-sinal.md) · [chassis-wizard-campanha.md](chassis-wizard-campanha.md) · [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md) · [registrar-sinal-lista-municipios.md](registrar-sinal-lista-municipios.md) · `actions/municipalityUpdate` · `campaignHomeActions.ts` (`register-signal`)
|
||||
- AGENTS.md — Local API `overrideAccess: false`
|
||||
- `PRODUCT.md` / `DESIGN.md` · Drawer B42/B34
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue