mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
docs: rascunho B92–B94 — sugestões no empty do wizard de município
Quebra o empty state do passo 1 dos wizards (/campanha/acoes) em três entregáveis: chassis+esquecidos (B92), continuity visitados/última ação (B93), geo mais próximo (B94). Aguarda gate de confirmação antes do agent:register. Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
This commit is contained in:
parent
d9a2414656
commit
16d0803c2a
3 changed files with 394 additions and 0 deletions
143
docs/plans/wizard-municipio-sugestoes-chassis.md
Normal file
143
docs/plans/wizard-municipio-sugestoes-chassis.md
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# Empty state do wizard — chassis + ranking de esquecidos
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe em `WizardMunicipalitySearchStep` (passo 1 dos wizards `/campanha/acoes/<slug>`)
|
||||
Appetite: ~1–1,5 dia eng; modo suggest no home-search + rank puro + lista no idle; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Clarity under pressure; ação→local) / `DESIGN.md` (register `product`) · tema `data-theme='campaign'` · B60 `WizardMunicipalitySearchStep` · B68 `HomeSearchHitRow` / suggest · B59 `CampaignWizardShell`.
|
||||
|
||||
Na implementação (`work-issue`): craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** assessor ou CG abre “Ajustar votos” / “Registrar sinal” no Início e cai no passo “Em qual município?” ainda sem digitar — hoje a região abaixo do input é deserto.
|
||||
- **Job principal:** com zero teclas, ver municípios **prováveis** (esquecidos / frios no escopo) e avançar com um toque; digitar continua refinando via B60.
|
||||
- **Estratégia de cor:** Restrained — mesmos hits do wizard; título de seção discreto; sem card.
|
||||
- **Edit where you see:** não — descoberta/navegação de fluxo (select = auto-avanço B60).
|
||||
- **Anti-goals:** mapa no empty; command palette; misturar hits de busca tipada com sugestões na mesma lista; motor E11 / `allocationDecision` neste passo; hero de KPI.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ /campanha/acoes/<slug> (sem ?municipio=) ──────────────┐
|
||||
│ [←] Ajustar votos [✕] │
|
||||
│ Em qual município? │
|
||||
│ ┌─ Buscar município ─────────────────────────────────┐ │
|
||||
│ │ Nome do município… │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ Prováveis │
|
||||
│ · Cairu · Baixo Sul · 2022 … [alta?] │
|
||||
│ · Valença · … │
|
||||
│ · … (até ~8; select = avança com ?municipio=) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
Fora do frame: strip de ações do Início; Continuity (B93)
|
||||
e geo (B94) entram depois como linhas prefixadas.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim — lista curta de municípios do escopo do ator, ranqueados por frescor + desempates operacionais.
|
||||
- **Decisões desbloqueadas:**
|
||||
- Assessor/CG: “em qual município desta carteira/estado eu executo **esta** ação agora, sem digitar o nome?”
|
||||
- Implicação: atacar primeiro quem está **frio** (sem sinal) e, empate, quem a campanha já investiu mais / tem tendência / classe / volume de votos mais relevante.
|
||||
- **Forma escolhida:** lista ranqueada (mesmo degrau B48/B60/B68 — `HomeSearchHitRow`) — **por quê** a decisão é “qual abrir”. **Rejeitado:** mapa; `CampaignMetricStrip`; chart; chips sem readout (perda de contexto 2022 já no hit).
|
||||
- **Profile:** até ~8 itens (`HOME_SEARCH_SUGGEST_LIMIT` / paridade B68); scoped ao access; relativo/local (nome + TI + readout 2022 opcional); **dias sem sinal** como chave primária (não % estadual).
|
||||
- **Anti-goals de dado:** sem % estadual absoluto; sem gauge; sem inventar métrica nova além de reusar `lastSignalAt`, E14, tendência, E10, votos 2022 já no scope/artifact.
|
||||
|
||||
## Contexto
|
||||
|
||||
**B60 ✓** entrega a etapa de busca de município nos wizards (`WizardMunicipalitySearchStep` + `POST /campanha/home-search` `mode: 'wizard-municipality'`). Com query inativa a região fica **idle vazia** — B60 adiou explicitamente “chips de recentes/prioritários”.
|
||||
|
||||
**B68 ✓** resolveu o empty da busca do **Início** (`mode: 'suggest'`, rank por papel: assessor = carteira fria; CG = prioritários `alta` × déficit). O wizard **não** consome esse modo: precisa de política própria (ação-primeiro, não omnibox) e de desempates que o pedido de produto (2026-08-01) nomeou: envolvimento (E14), tendência política, classe territorial (E10), número de votos.
|
||||
|
||||
Este item é o **chassis + ranking server de esquecidos**. Continuity (visitados / última ação) e geo ficam em **B93** / **B94**.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Quando `!query.isActive` no passo de município do wizard: mostrar seção **Prováveis** (copy craft; default “Prováveis”), não o deserto.
|
||||
- Novo modo tipado no `home-search` (recomendação: `mode: 'wizard-municipality-suggest'`) distinto de `suggest` (B68) e de `wizard-municipality` (B60 search) — política e payload próprios; staff-only (layout `acoes/` já gateia).
|
||||
- Rank puro client-safe em `src/lib/` (irmão de `homeSearchSuggest.ts`), pinado em unit:
|
||||
1. **Primário:** mais dias sem sinal primeiro (`lastSignalAt` via `resolveMunicipalityLastSignalAt`; `null` = mais frio).
|
||||
2. **Desempates (nesta ordem):** envolvimento E14 (maior `engagementLevelRank` primeiro; `null`/`Sem nível` por último) → tendência (`desfavoravel` > `neutra` > `favoravel` > `null`) → classe E10 (`territorialClassSortWeight` desc; `sem_base`/`null` por último) → votos 2022 do candidato no município (maior primeiro; sem série = por último) → nome `pt-BR`.
|
||||
- Cap ~8; access `overrideAccess: false`; assessor nunca vê fora da carteira.
|
||||
- Digitar (≥ limiar B47) **substitui** sugestões pelos hits B60 (não misturar).
|
||||
- Select = `wizardActionHref` (auto-avanço intacto).
|
||||
- Sem migration / Consent / collection nova.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Modo dedicado `wizard-municipality-suggest`, não reusar `suggest` do Início.** Wizard é ritual ação→local; B68 filtra CG a `priority === 'alta'` e usa déficit — aqui o pedido é **esquecidos do escopo inteiro** com desempates E14/tendência/classe/votos. **Rejeitado:** chamar `loadHomeSearchSuggestions` no wizard (política errada); search com `q=""` (contradiz B66/B68).
|
||||
- **Primário = frescor (`lastSignalAt`), não só `lastUpdateAt`.** Mesma semântica E9/B68 (max update × pledge). **Rejeitado:** só `municipality.updatedAt` do Payload (ruído de admin); só `lastUpdateAt` (ignora pledges).
|
||||
- **Desempates na ordem do pedido de produto** (envolvimento → tendência → classe → votos), com pesos já existentes (`engagementLevelRank`, ordem de tendência acima, `territorialClassSortWeight`, votos nominais 2022 do artifact/scope). **Rejeitado:** déficit de meta como desempate neste item (B68 já cobre no Início; misturar duas “inteligências” no mesmo empty confunde); LQ contínuo no lugar da classe.
|
||||
- **Lista flat com título de seção; reason labels por linha ficam para B93/B94** (neste slice todas as linhas são “esquecidos”). **Rejeitado:** cards; multi-grupo neste appetite.
|
||||
- **i18n / naming:** identificadores `wizardMunicipalitySuggest`, `rankWizardMunicipalitySuggestions`, `loadWizardMunicipalitySuggestions`; strings pt-BR (“Prováveis”, …).
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Título da seção?** **Opções:** A “Prováveis” | B “Sugestões” (paridade B68) | C “Para esta ação”. **Recomendação:** **A** — distingue do omnibox do Início. _(assumido — craft)_
|
||||
- **CG inclui municípios `priority !== 'alta'`?** **Opções:** A sim, escopo completo frio-primeiro | B só `alta` como B68. **Recomendação:** **A** — o pedido é esquecidos + desempates de investimento; prioridade alta já é sinal na linha (`showPriority`). _(assumido — validar com produto)_
|
||||
- **Votos = série Solla 2022 no município (artifact) vs estimativa central de pledges?** **Opções:** A votos 2022 nominais | B estimado central. **Recomendação:** **A** — estável, sem live cache, já no readout do hit. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Idle["WizardMunicipalitySearchStep !query.isActive"] --> Suggest["POST home-search mode=wizard-municipality-suggest"]
|
||||
Suggest --> Load["loadWizardMunicipalitySuggestions"]
|
||||
Load --> Scope["loadMunicipalityScope + extraSelect"]
|
||||
Scope --> Rank["rankWizardMunicipalitySuggestions"]
|
||||
Rank --> Rows["HomeSearchHitRow + wizardActionHref"]
|
||||
Type["query.isActive"] --> Search["mode=wizard-municipality B60"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`src/lib/wizardMunicipalitySuggest.ts`** (puro): input tipado (`slug`, `name`, `lastSignalAt`, `engagementLevel`, `politicalTrend`, `territorialClass`, `votes2022`) + `rankWizardMunicipalitySuggestions(inputs, limit)` + constantes de ordem de tendência; unit-pinned.
|
||||
- **`src/utilities/homeSearch/loadWizardMunicipalitySuggestions.ts`** (`server-only`): `loadMunicipalityScope` com `extraSelect` (`lastUpdateAt`, `engagementLevel`, `politicalTrend`, …); classe via helper E10 já usado na lista/map; votos 2022 do artifact/`bahiaElectionAggregates` (ou campo já no scope se existir); `overrideAccess: false`.
|
||||
- **Rota** `home-search/route.ts` + schema zod discriminado: ramificar o novo mode; resposta no shape `WizardMunicipalitySearchSuccessResponse` (ou irmão com `resultKind: 'wizard-suggest'`).
|
||||
- **`WizardMunicipalitySearchStep`**: fetch suggest no idle; cancelar ao digitar; render seção + hits; empty tipado só quando search ativo e 0 hits.
|
||||
- **Reuse:** `HomeSearchHitRow`, `HomeSearchMunicipalityVoteTrailing`, `postCampaignJson`, `useHomeSearchQuery`, copy em `campaignWizardCopy.ts`.
|
||||
- **Migration:** Sem migration, sem collection, sem Consent.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Duras: nenhuma aberta (B59 ✓, B60 ✓).
|
||||
- Soft: B68 ✓ (padrão suggest ≠ search); E9/E10/E14 ✓ (sinais de desempate); artifact TSE para votos 2022.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Visitados recentes / última ação do ator → **B93**.
|
||||
- Município mais próximo (geo) → **B94**.
|
||||
- Empty da busca do Início (já **B68**); unificar políticas B68↔wizard.
|
||||
- Motor E11 / fila de alocação no wizard.
|
||||
- Leader (wizards são staff-only).
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Unificar `rankHomeSearchSuggestMunicipalities` e o rank do wizard num “suggest engine” genérico.** Se alguém “só completar”: API parametrizada prematura. **Mitigação:** dois módulos puros até 3º call site (precedente depth check).
|
||||
- **Carregar coverage/déficit/E11 no loader do wizard.** Explode latência do idle. **Mitigação:** só campos de desempate listados; déficit fica no B68.
|
||||
- **Reason chips + multi-fonte neste PR.** Mistura continuity/geo sem contrato de merge. **Mitigação:** B93/B94.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Comparator compartilhado de frescor** entre B68 e wizard. Revisitar no 3º call site (ledger O0+/DRY).
|
||||
- **RSC embed do suggest** (evitar POST no mount do passo). Revisitar se latência idle for P1 em campo.
|
||||
- **Reason label “Sem atualização há N dias” na linha.** Revisitar se craft/B93 pedir diferenciação visual além da ordem.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue — (após register)
|
||||
- [`WizardMunicipalitySearchStep.tsx`](../../src/components/campaign/shared/WizardMunicipalitySearchStep.tsx)
|
||||
- [`homeSearchSuggest.ts`](../../src/lib/homeSearchSuggest.ts) / [`loadHomeSearchSuggestions.ts`](../../src/utilities/homeSearch/loadHomeSearchSuggestions.ts)
|
||||
- [`busca-municipio-wizard.md`](busca-municipio-wizard.md) (B60 — adiados)
|
||||
- [`sugestoes-busca-vazia-inicio.md`](sugestoes-busca-vazia-inicio.md) (B68)
|
||||
- [`engagementLevel.ts`](../../src/lib/engagementLevel.ts) · [`territorialClassSortWeight.ts`](../../src/lib/territorialClassSortWeight.ts)
|
||||
- AGENTS.md — access advisor; `overrideAccess: false`; naming EN / copy pt-BR
|
||||
- `PRODUCT.md` / `DESIGN.md` — Clarity under pressure; Field Desk
|
||||
126
docs/plans/wizard-municipio-sugestoes-continuity.md
Normal file
126
docs/plans/wizard-municipio-sugestoes-continuity.md
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# Empty state do wizard — continuity (visitados + última ação)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P2
|
||||
Model: composer-2.5
|
||||
Impeccable: B — prefixo client no empty de `WizardMunicipalitySearchStep` (após B92)
|
||||
Appetite: ~0,75–1 dia eng; merge client de recentVisits + last-acted localStorage; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Feel the action; Clarity under pressure) / `DESIGN.md` · tema `campaign` · B92 chassis · `recentVisits.ts` · Quadro `RecentlyVisitedCard`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona:** staff que **acabou** de mexer num município ou voltou ao ritual no mesmo aparelho — quer repetir o local sem re-digitar.
|
||||
- **Job principal:** no idle do passo 1, ver no topo “onde eu estava / onde eu agi” antes dos esquecidos server.
|
||||
- **Estratégia de cor:** Restrained; secondary discreto na linha (“Visitado”, “Última ação”).
|
||||
- **Edit where you see:** não.
|
||||
- **Anti-goals:** sync multi-dispositivo; POST de histórico ao servidor; misturar continuity com hits tipados; redesign do card do Quadro.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ Wizard passo município (idle, pós-B92) ───────────────┐
|
||||
│ [input busca…] │
|
||||
│ Prováveis │
|
||||
│ · Itabuna · … · Última ação │
|
||||
│ · Cairu · … · Visitado │
|
||||
│ · Valença · … · Visitado │
|
||||
│ · … (fill B92 esquecidos, dedup) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
Geo (B94) pode ocupar 1 slot acima de Continuity.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim — atalhos de continuidade (client) mesclados à lista server B92.
|
||||
- **Decisões:** “volto ao município em que eu **agi** ou que **abri** há pouco?”
|
||||
- **Forma:** mesmas linhas `HomeSearchHitRow` + secondary/reason curto. **Rejeitado:** seção separada com cards; timeline.
|
||||
- **Profile:** 1 última ação + até ~3–5 visitados município; dedup com geo/esquecidos; cap total da lista ~8.
|
||||
- **Anti-goals:** sem enviar `localStorage` ao servidor; sem PII além do que já vive no device (padrão visitados).
|
||||
|
||||
## Contexto
|
||||
|
||||
B92 entrega o idle com esquecidos server. O pedido de produto (2026-08-01) também pede: (a) **últimos municípios acessados**; (b) **último município em que o usuário executou qualquer ação**.
|
||||
|
||||
Já existe `teqo:campaign:recent-visits` (`recentVisits.ts`, Quadro). B68 **adiou** visitados no suggest do Início por hidratação — no wizard o idle já é client (`WizardMunicipalitySearchStep`), então o merge é natural **depois** do mount.
|
||||
|
||||
“Última ação” **não** tem índice server hoje (autores espalhados: `municipalityUpdate.author`, `votePledge.declaredBy`/`estimatedBy`, `campaignDemand.createdBy`, …). Índice unificado = migration/query cara. Neste item: **localStorage device-local**, escrito nos success paths dos wizards/actions de município.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Após mount, com `!query.isActive`: mesclar no topo da lista B92:
|
||||
1. **Última ação** (0–1) — slug gravado em storage dedicado.
|
||||
2. **Visitados recentes** (`kind === 'municipality'`) — extrair slug do `href`, filtrar ao escopo se o server já devolveu o universo **ou** validar contra hits suggest / lista acessível passada como prop.
|
||||
- Dedup estável: geo (B94) > última ação > visitados > esquecidos B92; uma linha por slug.
|
||||
- Reason labels pt-BR curtos na secondary ou trailing textual.
|
||||
- Gravar `lastActedMunicipality` (nome final no craft) ao **sucesso** de writes do ritual (ajuste votos, sinal, tendência, liderança, demanda — paths que já fecham wizard com município). Fail-soft se storage bloquear.
|
||||
- Sem migration / Consent / envio de lat-lng ou histórico ao server.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Continuity 100% client (localStorage), não query server “last modified by me”.** Barato, mesmo padrão de visitados; cross-device fica Adiado. **Rejeitado:** UNION multi-collection por `author`/`declaredBy` neste appetite (latência + índices + access); collection `municipalityActorTouch` (migration cara sem evidência).
|
||||
- **Gravar última ação no success do write, não no select do passo 1.** Select sem commit não é “executou ação”. **Rejeitado:** gravar no `router.push(?municipio=)` (falso positivo se abandonar).
|
||||
- **Reusar `recentVisits` para acessos; storage separado para last-acted.** Semânticas diferentes (dwell 2s vs write). **Rejeitado:** sobrecarregar `RecentVisitEntry` com `kind: 'acted'`.
|
||||
- **Merge só no idle; search tipado ignora continuity.** Paridade B68/B92.
|
||||
- **i18n:** `campaignLastActedMunicipality` / `recordLastActedMunicipality` / `listWizardContinuitySlugs`; copy “Última ação”, “Visitado”.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Quantos visitados no prefixo?** **Opções:** A 3 | B 5 | C todos os 8 do storage. **Recomendação:** **A** (3) — deixa espaço para esquecidos/geo no cap 8. _(assumido)_
|
||||
- **Escopo assessor:** visitado fora da carteira (mudança de advisors)? **Opções:** A ocultar | B mostrar e falhar no select. **Recomendação:** **A** — filtrar contra slugs do payload suggest ou prop `accessibleSlugs`. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Mount["client mount idle"] --> Server["hits B92"]
|
||||
Mount --> Local["recentVisits + lastActed"]
|
||||
Local --> Merge["mergeWizardMunicipalitySuggestions"]
|
||||
Server --> Merge
|
||||
Merge --> Rows["HomeSearchHitRow + reason"]
|
||||
Write["wizard action success"] --> Record["recordLastActedMunicipality(slug)"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`src/lib/campaignLastActedMunicipality.ts`** (client-safe storage helpers, irmão fino de `recentVisits.ts`).
|
||||
- **`src/lib/wizardMunicipalitySuggestMerge.ts`** (puro): ordena/dedup fontes `{ source, slug }[]` + hits server → lista final com `reason`.
|
||||
- **`WizardMunicipalitySearchStep`**: após fetch B92, merge client; skeleton/idle sem flash errado (não render continuity no SSR).
|
||||
- **Hooks de gravação:** pontos de sucesso já existentes nos formActions/wizards (votos, sinal, tendência, liderança, demanda) — uma chamada `recordLastActedMunicipality(slug)`; sem abstração genérica se <3 sites usar helper inline até o 3º.
|
||||
- **Migration:** nenhuma.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: **B92**.
|
||||
- Soft: visitados ✓; wizards A1–A5 ✓.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Geo → **B94**.
|
||||
- Last-acted server / multi-device.
|
||||
- Continuity no empty do Início (B68 adiou; gatilho separado).
|
||||
- Alterar card do Quadro.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Helper genérico “campaignLocalShortcuts”.** **Mitigação:** dois módulos pequenos até 3º consumidor.
|
||||
- **Gravar last-acted em toda mutation Payload via hook global.** Blast radius. **Mitigação:** só success paths do ritual de ação.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Last-acted server (cross-device).** Revisitar se o mesmo CG reportar “no celular não aparece o que fiz no notebook” em sessão observada.
|
||||
- **Visitados no suggest do Início (B68 adiado).** Revisitar com evidência de sessão; não neste item.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue — (após register)
|
||||
- [`recentVisits.ts`](../../src/utilities/recentVisits.ts) · [`visitados-recentemente.md`](visitados-recentemente.md)
|
||||
- [`wizard-municipio-sugestoes-chassis.md`](wizard-municipio-sugestoes-chassis.md) (B92)
|
||||
- AGENTS.md — sem Consent para storage local de UX autenticada (precedente B14/visitados)
|
||||
125
docs/plans/wizard-municipio-sugestoes-geo.md
Normal file
125
docs/plans/wizard-municipio-sugestoes-geo.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# Empty state do wizard — município mais próximo (geo)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P2
|
||||
Model: composer-2.5
|
||||
Impeccable: B — 1 linha prefixada no empty de `WizardMunicipalitySearchStep` (após B92)
|
||||
Appetite: ~1 dia eng; reuso B14 (`campaignGeolocation` + `municipalityProximity`) no idle do wizard; sem migration / Consent
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Feel the action) / `DESIGN.md` · tema `campaign` · B14 `NearestMunicipalityCard` · B92 chassis · B93 merge order.
|
||||
|
||||
Na implementação: craft compacto → critique → polish. Gatilho `harden` se permission/prompt edge cases vazarem copy inconsistente com B14.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona:** staff em deslocamento (carro/praça) abre uma ação no celular e quer o município **onde está**.
|
||||
- **Job principal:** no idle, um toque no “mais próximo” avança o wizard com o município resolvido.
|
||||
- **Estratégia de cor:** Restrained; reason “Perto de você” / distância se B14 já formata.
|
||||
- **Edit where you see:** não.
|
||||
- **Anti-goals:** mapa embutido no wizard; POST de lat/lng; Consent novo; prompt agressivo a cada abertura de ação; card GPS gamificado.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ Wizard passo município (idle) ────────────────────────┐
|
||||
│ [input…] │
|
||||
│ Prováveis │
|
||||
│ · Salvador — ZE 5 · … · Perto de você (~1,2 km) │
|
||||
│ · … (B93 continuity) │
|
||||
│ · … (B92 esquecidos) │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
Sem permissão / fora da BA / unsupported: linha some;
|
||||
lista B92/B93 permanece.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
- **Vou apresentar dados?** Sim — 0–1 município resolvido por geo no escopo do ator.
|
||||
- **Decisões:** “executo a ação **aqui** (onde estou)?”
|
||||
- **Forma:** uma linha na lista (pobre). **Rejeitado:** card “Onde estou” duplicando o Quadro; mapa Leaflet no passo.
|
||||
- **Profile:** 1 item; client-only position; match contra malha já usada em B14.
|
||||
- **Anti-goals:** sem transmitir posição ao servidor; sem reverse-geocode vendor.
|
||||
|
||||
## Contexto
|
||||
|
||||
B14 ✓ no **Quadro** (`NearestMunicipalityCard`): posição no browser, containment na malha municipal (+ ZE Salvador), fallback Haversine, prompt 1× por sessão de aba, sem Consent.
|
||||
|
||||
O pedido (2026-08-01) inclui o mesmo sinal no empty do **wizard**. O passo 1 hoje não carrega geometrias nem lista `AccessibleMunicipality` — B92 traz hits server; este item acrescenta a resolução client e o slot no merge (ordem: geo → continuity B93 → esquecidos).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- No idle do wizard, tentar resolver município mais próximo **no escopo do ator** reusando `requestCurrentPosition` / `resolveNearbyMunicipality` / loaders lazy de geometria.
|
||||
- Página `acoes/[slug]` (passo busca) passa prop `accessibleMunicipalities: AccessibleMunicipality[]` (slug/name/ibgeCode) a partir do scope — ou o client usa só os slugs do suggest B92 **mais** um payload mínimo RSC (recomendação: prop RSC, porque suggest é top-8 e o nearest pode estar fora do top frio).
|
||||
- Prompt automático: **respeitar** `hasPromptedThisSession` de B14 (não re-assaltar se o Quadro já pediu; se wizard é a 1ª superfície da sessão, um prompt basta).
|
||||
- Falhas (`denied` / `timeout` / `unsupported` / fora da BA / outOfScope sem nearest): **silêncio** — não montar linha de erro no ritual (diferente do card do Quadro que explica); usuário digita.
|
||||
- `kind: 'inScope'` → uma hit row com reason; select = auto-avanço.
|
||||
- Sem migration / Consent / POST de coordenadas.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Client-only matching (paridade B14).** **Rejeitado:** POST lat/lng; PostGIS; artefato de centroides commitado (já revertido em B14).
|
||||
- **Prop RSC de municípios acessíveis** para o matcher (não só top-8 do suggest). **Rejeitado:** resolver só dentro dos 8 esquecidos (falso “perto” se o município real ficou de fora do rank frio).
|
||||
- **Fail soft sem UI de erro no wizard.** Ritual não deve virar troubleshooting de GPS. **Rejeitado:** copiar o card B14 inteiro para dentro do shell.
|
||||
- **Uma linha, não card.** **Rejeitado:** embutir `NearestMunicipalityCard`.
|
||||
- **i18n:** reusar `formatDistanceKm`; reason “Perto de você”; helpers `useWizardNearestMunicipality` se o efeito merecer nome (senão lógica no step até extrair no 2º call site).
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Prompt automático no mount do wizard se a sessão ainda não pediu?** **Opções:** A sim (paridade Quadro) | B só se permissão já `granted` | C CTA explícito “Usar minha localização”. **Recomendação:** **B** no v1 do wizard (zero surpresa no meio do ritual) + se `granted`, resolve quieto; CTA curto se `prompt`/`denied` e craft quiser. _(assumido — validar com produto; se campo pedir paridade total com Quadro, subir para A)_
|
||||
- **Salvador multi-ZE outOfScope / zoneCity:** seguir B14 (lista filtrada no Quadro). No wizard: **Opções:** A omitir linha | B deep-link impossível sem município. **Recomendação:** **A** omitir — wizard exige `municipality` atômico. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Page["acoes/slug page RSC"] --> Prop["accessibleMunicipalities"]
|
||||
Prop --> Step["WizardMunicipalitySearchStep"]
|
||||
Step --> Geo["permission + position B14 utils"]
|
||||
Geo --> Mesh["lazy bahiaGeometries + zones"]
|
||||
Mesh --> Resolve["resolveNearbyMunicipality"]
|
||||
Resolve --> Merge["slot geo no merge B93/B92"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`acoes/[slug]/page.tsx`**: ao renderizar o search step, carregar scope enxuto (id/slug/name/ibgeCode) e passar como prop.
|
||||
- **Ilha no step / hook:** permission + optional locate; dynamic import geometria; resolver; emitir `{ slug, reason, distanceKm? }` ou `null`.
|
||||
- **Merge** (contrato com B93): fonte `geo` tem precedência máxima de ordenação.
|
||||
- **Reuse:** `campaignGeolocation.ts`, `municipalityProximity.ts`, `loadMunicipalityGeometryModule`, `formatDistanceKm`.
|
||||
- **Migration:** nenhuma. **Consent:** nenhum.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Dura: **B92**.
|
||||
- Soft: **B93** (merge order — se B94 landar antes, step aceita geo sem continuity; merge helper pode nascer em qualquer um e o outro consome).
|
||||
- Soft: B14 ✓.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Continuity → **B93**.
|
||||
- Ranking esquecidos → **B92**.
|
||||
- Mudanças no card do Quadro.
|
||||
- Geo para `leader`.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Extrair `NearestMunicipalityCard` → package compartilhado pesado.** **Mitigação:** reusar utils puros; UI do wizard é uma hit row.
|
||||
- **Prefetch da malha no Início para o wizard.** **Mitigação:** lazy no mount do step; memo do módulo já ajuda se Quadro carregou antes.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **CTA “Usar localização” quando permission ≠ granted.** Revisitar se sessão observada mostrar staff recusando o prompt silencioso e pedindo botão.
|
||||
- **Centroid artifact** só se o lazy da malha pesar no mid-tier Android em campo.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue — (após register)
|
||||
- [`municipio-mais-proximo.md`](municipio-mais-proximo.md) (B14)
|
||||
- [`campaignGeolocation.ts`](../../src/utilities/campaignGeolocation.ts) · [`municipalityProximity.ts`](../../src/lib/municipalityProximity.ts)
|
||||
- [`wizard-municipio-sugestoes-chassis.md`](wizard-municipio-sugestoes-chassis.md) (B92)
|
||||
- AGENTS.md — sem Consent para geo local autenticada (B14)
|
||||
Loading…
Reference in a new issue