mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:25:18 +00:00
Merge pull request #144 from fsolla/cursor/0ce71e2d
B106–B108 — planos polish mobile busca/wizard
This commit is contained in:
commit
3babd78d73
3 changed files with 400 additions and 0 deletions
133
docs/plans/inicio-busca-voltar-skeleton.md
Normal file
133
docs/plans/inicio-busca-voltar-skeleton.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# Início mobile — Voltar no header + Android back + skeleton das sugestões
|
||||
|
||||
Status: registrado
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: #141
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe em `CampaignMobileTopBar` + chrome da busca do Início (`uiFocused`)
|
||||
Appetite: ~0,75–1d eng; contexto de chrome + history layer + skeleton; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Clarity under pressure; Feel the action) / `DESIGN.md` · B66 modo focado · B68 sugestões · tema `data-theme='campaign'`.
|
||||
|
||||
Na implementação (`work-issue`): craft compacto → critique → polish (Android WebView / Chrome back + iOS Safari).
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** assessor/CG no Início mobile toca a busca; chrome retraí (B66); hoje o hamburger do header ainda sugere “abrir menu”, não “sair da busca”.
|
||||
- **Job principal:** no modo focado, o canto esquerdo do header vira **Voltar** (fecha a expansão); o botão Voltar do Android faz o mesmo — não navega para a rota anterior; enquanto as sugestões carregam, a região mostra skeleton shimmer.
|
||||
- **Estratégia de cor:** Restrained — ícone/botão ghost no Mandate Red (paridade wizard B75); skeleton `bg-muted` / `animate-pulse` do kit.
|
||||
- **Edit where you see:** não — chrome de descoberta.
|
||||
- **Anti-goals:** teleportar o input para o header; Sheet full-screen; aplicar o mesmo history layer ao bottom drawer (B91/B105) neste item; redesenhar sugestões.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ header app (Mandate Red) — busca NÃO focada ───────┐
|
||||
│ [☰] Jorge Solla / Campanha · Bahia [🔔] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
┌─ header app — busca focada (uiFocused) ─────────────┐
|
||||
│ [←] Jorge Solla / Campanha · Bahia [🔔] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
┌─ dock busca + região resultados ────────────────────┐
|
||||
│ ┌─ Buscar… ──────────────────────────────────────┐ │
|
||||
│ │ … │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ ░░░░░ skeleton row ░░░░░ (enquanto suggest load) │
|
||||
│ ░░░░░ skeleton row ░░░░░ │
|
||||
│ ░░░░░ skeleton row ░░░░░ │
|
||||
│ (depois) hits B68 / busca tipada │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
Fora do frame: strip/resumo retraídos (B66); drawer B91 N/A no Início.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — chrome + pending visual; hits/sugestões já existem (B68). Sem métrica nova.
|
||||
|
||||
## Contexto
|
||||
|
||||
**B66 ✓** retrai strip/resumo ao `uiFocused` (`homeSearchUiFocused`), mas `CampaignMobileTopBar` em modo `app` continua com `SidebarTrigger`. **B68 ✓** carrega sugestões no empty; a região fica vazia até `success` — só `aria-busy`, sem skeleton.
|
||||
|
||||
O provider da busca no Início vive **dentro** da página (`CampaignHomeStaffChrome` → `CampaignGlobalSearchProvider`); o top bar está no layout `(app)` **acima** do scroll. Não há `pushState`/`popstate` no `src/` hoje — o Voltar do Android sai da rota.
|
||||
|
||||
Pedido de produto (2026-08-01): no modo expandido, Voltar à esquerda no header (no lugar do menu); Android back recolhe a busca; skeleton shimmer no empty enquanto sugestões carregam.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Com `uiFocused` no Início mobile: substituir `SidebarTrigger` por botão **Voltar** (`ArrowLeft` + aria “Fechar busca” / “Voltar”) que chama o mesmo collapse que blur+Escape (clear focus; se query vazia, sai do modo focado — alinhado B66).
|
||||
- Ao entrar em `uiFocused`, empilhar um entry de history (`pushState` com marca estável); `popstate` / back do SO **só** recolhe a busca se o entry for nosso; não dispara navegação Next para a rota anterior.
|
||||
- Enquanto suggest/search estiver `loading`/`isFetching` sem hits ainda renderizados: 3–5 rows skeleton (altura ~hit row), `aria-busy` mantido; sumir ao `success`/`error`.
|
||||
- Desktop (`md+`): top bar já hidden — comportamento inalterado; history layer só mobile ou só quando top bar troca o trigger.
|
||||
- Sem migration / Consent / mudança de contrato JSON de busca.
|
||||
- Pins: unit no helper de history (push/pop/idempotência); unit/RTL do top bar app-mode com chrome “search-focused”; e2e leve opcional se estável.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Contexto leve de chrome no layout (irmã do wizard chrome), não subir o `HomeSearchProvider` inteiro.** O top bar só precisa de `{ focused, collapse }`. Home (e só Início) publica; top bar consome. **Rejeitado:** prop-drill via layout RSC; ler DOM `data-home-focused`; duplicar estado de foco no top bar.
|
||||
- **History = camada só do modo focado da busca do Início.** Marca tipo `teqo:home-search-focus` (nome final no craft). Collapse via back **não** limpa query tipada se produto preferir só blur focus — **recomendação:** igual Escape/`clear` do B66 (query some e chrome restaura) para um gesto único “sair da busca”. **Rejeitado:** history também no drawer B91 neste item; `beforeunload` hacks.
|
||||
- **Skeleton reutiliza `Skeleton` shadcn + shape das hit rows; sem lib de shimmer.** Extrair helper/componente mínimo se **B107** (wizard) for o 2º call site no mesmo ciclo — gate 2026-08-01 pediu skeleton no wizard também. **Rejeitado:** spinner centrado; skeleton só no input.
|
||||
- **Escopo de Voltar/history = Início (`/campanha`), não drawer global.** Drawer tem snap/peek próprios (B100/B105). **Rejeitado:** unificar “qualquer busca focada” neste PR.
|
||||
- **i18n:** ids `homeSearchChrome` / `collapseHomeSearch`; copy pt-BR no aria do Voltar.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Voltar com query tipada: limpa query ou só blur?** **Opções:** A limpa (paridade Escape) | B só tira foco e mantém texto (chrome pode continuar focado via `query.isActive`). **Recomendação:** **A** — um gesto = sair da busca. _(assumido — validar com produto)_
|
||||
- **Quantas rows de skeleton?** **Opções:** A 3 | B 5. **Recomendação:** **A** (viewport curto com teclado). _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Focus["input focus / uiFocused"] --> Pub["HomeSearchChrome publish"]
|
||||
Pub --> Bar["CampaignMobileTopBar: ☰ → Voltar"]
|
||||
Focus --> Hist["pushState marca"]
|
||||
Back["Android back / Voltar"] --> Pop["popstate ou collapse()"]
|
||||
Pop --> Clear["clear + uiFocused=false"]
|
||||
Fetch["suggest loading"] --> Skel["HomeSearchSuggestSkeleton"]
|
||||
Skel --> Hits["success → HomeSearchHitRow"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`CampaignHomeSearchChromeContext`** (ou nome alinhado ao wizard): provider no `(app)/layout` junto aos outros chromes; `useSetHomeSearchChrome` / `useHomeSearchChrome` espelhando o padrão B75.
|
||||
- **`CampaignGlobalSearchMount` / `CampaignHomeSearch` / controller:** ao mudar `uiFocused`, publish + sync history (push ao true, replace/back ao false sem loop).
|
||||
- **`CampaignMobileTopBar` (modo app):** se chrome.searchFocused → botão Voltar; senão `SidebarTrigger`.
|
||||
- **`HomeSearchSuggestSkeleton`** (inline no grupo de resultados / results shell): rows quando `isFetching && !hits`; reuso em Início; wizard município **fora** deste item (pode citar como soft follow-up).
|
||||
- **Helper puro** `homeSearchFocusHistory.ts` em `lib/`: serialize marca, `shouldHandlePopstate`, unit-pinned.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Duras: nenhuma. Soft: B66 ✓, B68 ✓. Paralelo a B102/B103/B105 (drawer) — não serializa schema; evitar editar os mesmos arquivos do drawer no mesmo PR se outro agente estiver neles.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Bottom drawer busca (B91/B100/B105) — Voltar/history/skeleton do drawer.
|
||||
- Remover título “Sugestões” → **B103**.
|
||||
- Wizard município sticky/título/**skeleton do passo** → **B107** (pode reusar o bloco de rows deste item). Autofocus/votos → **B108**.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **“Já que o top bar lê foco, movo a busca para o header.”** **Mitigação:** só swap do trigger; input fica no dock (B65/B66).
|
||||
- **History API × App Router (bfcache, soft nav).** **Mitigação:** marca + guard idempotente; testes unit do helper; não `router.back()` para collapse.
|
||||
- **Skeleton shared com API inchada.** **Mitigação:** um componente de N rows pinado; sem tema/variant factory.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Mesmo Voltar/history no drawer.** Revisitar quando B105 estiver estável e produto pedir paridade.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #141
|
||||
- `src/components/campaign/shell/CampaignMobileTopBar.tsx`
|
||||
- `src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx`
|
||||
- `src/components/campaign/dashboard/CampaignGlobalSearchMount.tsx`
|
||||
- `src/lib/campaignHomeSearchContract.ts`
|
||||
- [modo-focado-busca-no-focus.md](modo-focado-busca-no-focus.md) (B66) · [sugestoes-busca-vazia-inicio.md](sugestoes-busca-vazia-inicio.md) (B68)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
136
docs/plans/wizard-foco-input-e-votos.md
Normal file
136
docs/plans/wizard-foco-input-e-votos.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Wizard — autofocus do primeiro input + destaque do último cenário focado
|
||||
|
||||
Status: registrado
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: #143
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe em `CampaignWizardShell` + steps com input + `VoteEstimateScenarioInputs` compact
|
||||
Appetite: ~0,5–0,75d eng; foco/a11y + CSS de active scenario; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Feel the action; Clarity under pressure) / `DESIGN.md` · B59 shell · B61 votos · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish (teclado mobile ao abrir cada step).
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** assessor avança steps do wizard no celular; espera digitar na hora sem tocar de novo no campo.
|
||||
- **Job principal:** ao abrir um step **com input**, o primeiro campo já está focado (e selecionado se numérico); no ajuste de votos, o input **visualmente** destacado é o **último focado**, não o central fixo — alinhado aos atalhos (+50, 2×) que já seguem `focusedScenario`.
|
||||
- **Estratégia de cor:** Restrained — reusar `border-primary/50 bg-primary/5 ring-primary/15` no ativo; muted nos demais.
|
||||
- **Edit where you see:** sim no sentido de affordance do cenário ativo (já existe strip); não abre editor novo.
|
||||
- **Anti-goals:** autofocus em steps só de escolha (grid de tendência/tipo) sem input de texto; mudar validação `getWizardVoteViolation`; spreadsheet mode.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ step município (após B107 sem h1) ─────────────────┐
|
||||
│ ┌─ Nome do município… │← foco + teclado ao abrir │
|
||||
│ hits… │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ step votos estimados ──────────────────────────────┐
|
||||
│ [strip marker sob o ativo] │
|
||||
│ ┌─────┐ ┌─────┐ ┌─────┐ │
|
||||
│ │ 80 │ │ 100 │ │ 120 │ ← destaque = last focused │
|
||||
│ └─────┘ └─────┘ └─────┘ │
|
||||
│ (ex.: foco em otimista → anel primary nele, │
|
||||
│ não no central) │
|
||||
│ [+50] [2×] … aplicam ao focusedScenario │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — affordance de edição; números já existentes.
|
||||
|
||||
## Contexto
|
||||
|
||||
`CampaignWizardShell` faz `titleRef.focus()` em todo `stepTitle` change — útil para screen readers em steps de escolha, mas **rouba** o foco do primeiro input.
|
||||
|
||||
`WizardExpectedVotesStep` já mantém `focusedScenario` (default `central`) e passa `activeScenario` / `onFocusScenario` para atalhos e strip (`markerMode: 'active-only'`). Porém em `VoteEstimateScenarioInputs` **compact**, a classe primary está hardcoded em `scenario === 'central'`, então o meio parece sempre “o” ativo mesmo quando o foco (e os botões) estão noutro cenário.
|
||||
|
||||
Pedido de produto (2026-08-01): autofocus do primeiro input em cada step com input; no votos, destaque = último focado.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Steps com `<input>` / textarea / search: ao montar (ou ao mudar de step), focar o primeiro campo editável; em campos numéricos de votos, manter `select()` no focus (já existe).
|
||||
- Shell **não** foca o `h1` quando o step declara que vai autofocusar um input (ou quando `stepTitle` é null — B107).
|
||||
- Compact votes: highlight primary segue `activeScenario` (último `onFocus`), não `central` fixo; erro (`errorScenarios`) continua a ganhar da highlight de foco.
|
||||
- Default inicial inalterado: monta com `central` focado + `autoFocusScenario="central"` → meio destacado até o usuário focar outro.
|
||||
- Sem migration / mudança de schema de pledge.
|
||||
- Pins: unit na regra de classe compact (`activeScenario` vs central); RTL leve de autofocus no municipality step se barato.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Um item para autofocus + highlight de votos.** Mesmo eixo “foco = sinal do controle ativo”; appetite cabe. **Rejeitado:** duas Issues cosméticas.
|
||||
- **Opt-in por step (`autoFocusFieldId` / `focusMode: 'title' | 'first-input'`), não heurística DOM genérica no shell.** Cada step sabe seu primeiro input. **Rejeitado:** `querySelector('input')` no shell (frágil com hidden fields).
|
||||
- **Highlight compact = `activeScenario`, em todos os call sites compact (wizard + lista B42).** Consistência; lista já passa foco. **Rejeitado:** prop `highlightCentralAlways` legacy.
|
||||
- **Steps só de botões/grid (tendência, tipo de sinal): mantêm foco no título (ou primeiro radio/button se já existir).** Fora do “com input”. **Rejeitado:** forçar foco no primeiro botão do grid neste item (pode ser follow-up a11y).
|
||||
- **i18n:** ids existentes; sem copy nova obrigatória.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Município: `autoFocus` nativo vs `useEffect` + ref após paint?** **Opções:** A `autoFocus` no `CampaignSearchInput` | B effect no step. **Recomendação:** **A** se o shell parar de focar o h1; B se race com navegação pending. _(assumido — craft)_
|
||||
- **Labeled variant (não compact) também destaca por foco?** **Opções:** A só compact | B ambos. **Recomendação:** **A** — pedido é o wizard/strip compact; labeled já tem labels por campo. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Shell["CampaignWizardShell focusMode"]
|
||||
Mun["MunicipalitySearch autoFocus"]
|
||||
Votes["ExpectedVotes autoFocus central"]
|
||||
Note["TrendNote / SignalBody / Leadership autoFocus"]
|
||||
Compact["VoteEstimateScenarioInputs highlight=activeScenario"]
|
||||
Shell -->|"title ou none"| Mun
|
||||
Shell --> Votes --> Compact
|
||||
Shell --> Note
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`CampaignWizardShell`:** prop `contentFocus?: 'title' | 'none'` (default `'title'` para não regredir grids); quando `'none'`, skip `titleRef.focus()`. Steps com input passam `'none'`.
|
||||
- **`WizardMunicipalitySearchStep`:** `contentFocus="none"` + `autoFocus` no search input (coordena com B107 sem h1).
|
||||
- **`WizardExpectedVotesStep` / `WizardTrendNoteStep` / `WizardSignalBodyStep` / `WizardLeadershipStep`:** `contentFocus="none"` + autofocus no primeiro campo.
|
||||
- **`VoteEstimateScenarioInputs` compact:** trocar `scenario === 'central'` por `scenario === activeScenario` na classe primary (erro destrutivo prevalece).
|
||||
- **Testes:** unit da classe compact; smoke se houver spec do wizard votos.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Soft: B59 ✓, ajuste votos wizard ✓. Coordena com **B107** (shell sem h1 no município) — se B107 landar antes, `contentFocus` no município é trivial; se este landar antes, funciona com h1 ainda presente desde que shell não foque o título.
|
||||
- `serializes`: nenhum schema; preferir não editar `CampaignWizardShell` em paralelo com B107 no mesmo instante — se ambos claimed, **B107 primeiro** (título) ou um PR único se o mesmo agente pegar os dois.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Remover título / sticky busca → **B107**.
|
||||
- Voltar/skeleton Início → **B106**.
|
||||
- Mudar defaults de cenário (pessimista vs central) ou atalhos.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Autofocus + leitores de tela (WCAG 2.4.3).** **Mitigação:** steps de escolha continuam no título; só steps de formulário movem foco para o campo — padrão de wizards mobile.
|
||||
- **Autofocus em toda navegação client do wizard dispara teclado em loop.** **Mitigação:** focus só no mount do step / mudança de `stepTitle`+slug, não a cada re-render de pending.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Foco no primeiro controle de steps só-grid.** Revisitar com auditoria a11y se VoiceOver reclamar do h1 sem atalho para o grid.
|
||||
- **Sincronizar highlight da variant labeled.** Se a lista desktop compactar tudo em compact.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #143
|
||||
- `src/components/campaign/shared/CampaignWizardShell.tsx`
|
||||
- `src/components/campaign/shared/WizardMunicipalitySearchStep.tsx`
|
||||
- `src/components/campaign/shared/WizardExpectedVotesStep.tsx`
|
||||
- `src/components/campaign/votePledge/VoteEstimateScenarioInputs.tsx`
|
||||
- `src/components/campaign/municipality/WizardTrendNoteStep.tsx`
|
||||
- `src/components/campaign/municipality/WizardSignalBodyStep.tsx`
|
||||
- `src/components/campaign/leadership/WizardLeadershipStep.tsx`
|
||||
- [chassis-wizard-campanha.md](chassis-wizard-campanha.md) (B59) · [ajuste-votos-wizard.md](ajuste-votos-wizard.md)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
131
docs/plans/wizard-municipio-sem-titulo-sticky.md
Normal file
131
docs/plans/wizard-municipio-sem-titulo-sticky.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# Wizard município — sem título + busca sticky + skeleton
|
||||
|
||||
Status: registrado
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: #142
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe em `WizardMunicipalitySearchStep` + `CampaignWizardShell` (título opcional)
|
||||
Appetite: ~0,5–0,75d eng; markup/CSS + shell opt-out do h1 + skeleton idle; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Clarity under pressure) / `DESIGN.md` · B60/B75/B95 · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish (teclado aberto + lista longa).
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** CG/assessor no passo 1 do wizard no celular; o header vermelho já diz o fluxo (“Ajustar votos”); o placeholder é “Nome do município…”.
|
||||
- **Job principal:** cair direto na busca (sem h1 “Em qual município?”); ao rolar hits, a barra de busca **permanece** logo abaixo do header; enquanto suggest/search carrega, skeleton shimmer na região de resultados.
|
||||
- **Estratégia de cor:** inalterada; sticky com `bg-background` para não transparizar hits; skeleton `bg-muted` / `animate-pulse`.
|
||||
- **Edit where you see:** não — layout do passo.
|
||||
- **Anti-goals:** sticky do header vermelho (já fora do scroll); redesign da hit row; mudar ranking B92–B94.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ header wizard (Mandate Red) ───────────────────────┐
|
||||
│ Ajustar votos [X] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
┌─ scroll content ────────────────────────────────────┐
|
||||
│ ┌─ Buscar município (sticky) ────────────────────┐ │ ← cola sob o header
|
||||
│ │ Nome do município… │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ ░░░ skeleton · skeleton · skeleton ░░░ (loading) │
|
||||
│ Cairu · Baixo Sul (após success) │
|
||||
│ (rola; busca sticky fica) │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
Sem h1 “Em qual município?”. Gap shell→busca mínimo no mobile.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — layout/chrome; hits inalterados.
|
||||
|
||||
## Contexto
|
||||
|
||||
**B95 ✓** aproxima o título do header e remove bullets; o h1 **“Em qual município?”** (`WIZARD_MUNICIPALITY_STEP_TITLE`) permanece. `CampaignWizardShell` sempre renderiza o `h1` e foca nele no mount.
|
||||
|
||||
No mobile, a busca + lista rolam juntos dentro de `CampaignContentScroll` — a barra sobe e some sob o header ao escanear sugestões/hits.
|
||||
|
||||
Pedido de produto (2026-08-01): remover o título; diminuir gap ao header; busca sticky/fixed sempre sob o header; skeleton enquanto o empty/suggest (ou search) carrega — se tem load, tem load indicator.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Passo município **sem** `h1` “Em qual município?” (copy pode permanecer no módulo para sr-only/`aria-label` da região se útil; UI visível some).
|
||||
- Gap visual header→busca no mobile menor que o stack atual (sem h1 + `pt` já `pt-3` do B95; critique pode `pt-2` / `gap` só neste passo).
|
||||
- Barra de busca **sticky** no scrollport do conteúdo (`sticky top-0 z-10` + fundo opaco + padding-bottom curto), sempre visível sob o top bar vermelho.
|
||||
- Lista de resultados rola por baixo da busca sticky.
|
||||
- Enquanto `results.status === 'loading'` / `resultsBusy` sem hits: 3 rows skeleton (paridade B106), `aria-busy` mantido; sumir em `success`/`error`.
|
||||
- Sem migration / mudança de ranking / autofocus (autofocus → **B108**).
|
||||
- Pin: unit/RTL se classes sticky/`stepTitle` opcional forem pináveis; e2e não obrigatório.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **`stepTitle` opcional no shell (`string | null`): omitir `h1` quando vazio/null.** Passo município passa `null`. Outros passos inalterados. **Rejeitado:** CSS `hidden` no h1 (ainda no a11y tree / foco); fork de shell só para município.
|
||||
- **Sticky da busca, não `position: fixed` no viewport.** Sticky relativo ao `CampaignContentScroll` alinha sob o header sem calcular `env(safe-area)` + altura do top bar à mão. **Rejeitado:** `fixed` + spacer mágico (frágil com teclado).
|
||||
- **Skeleton no idle/loading deste passo (produto 2026-08-01).** Paridade com B106; reutilizar o mesmo bloco de rows se B106 extrair helper — 2º call site justifica shared mínimo. **Rejeitado:** só `aria-busy` sem visual; spinner centrado.
|
||||
- **Item novo (B107), não reabrir B95.** B95 manteve o título de propósito; produto agora remove. **Rejeitado:** editar só o as-built B95 sem Issue.
|
||||
- **i18n:** `WIZARD_MUNICIPALITY_STEP_TITLE` pode virar sr-only / aria da região; identificadores ingleses intactos.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Título some só no mobile ou em todo breakpoint?** **Opções:** A some sempre | B some só `md:hidden` (desktop mantém h1). **Recomendação:** **A** — contexto do fluxo já está no chrome/desktop trailing; placeholder basta. _(assumido — gate: aprovado implícito no lote)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Shell["CampaignWizardShell stepTitle=null → sem h1"]
|
||||
Step["WizardMunicipalitySearchStep"]
|
||||
Stick["wrapper sticky top-0 bg-background"]
|
||||
Input["CampaignSearchInput"]
|
||||
Skel["skeleton rows se loading"]
|
||||
List["ul hits / suggest"]
|
||||
Shell --> Step
|
||||
Step --> Stick --> Input
|
||||
Step --> Skel
|
||||
Step --> List
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`CampaignWizardShell.tsx`:** `stepTitle?: string | null`; se ausente, não renderiza `h1` / não foca título; `aria-labelledby` cai para id do flow no chrome ou `aria-label` no `main` via `flowTitle`.
|
||||
- **`WizardMunicipalitySearchStep.tsx`:** `stepTitle={null}`; envolver input (e label) em container sticky; lista fora do sticky; render skeleton quando busy sem hits.
|
||||
- **Skeleton rows:** reusar helper/componente nascido em **B106** (`HomeSearchSuggestSkeleton` ou equivalente em `shared`/`dashboard`) — se B107 landar antes, inline 3× `Skeleton` e B106 extrai no polish.
|
||||
- **`campaignWizardCopy.ts`:** título pode permanecer exportado para aria; documentar.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Soft: B60 ✓, B75 ✓, B95 ✓. Nenhuma dura.
|
||||
- Coordenar com **B108** (autofocus): sem h1, o shell não deve “roubar” foco — B108 formaliza; neste item, ao omitir h1, **não** chamar `titleRef.focus()` (já coberto pela ausência do nó).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Autofocus do input → **B108**.
|
||||
- Destaque de cenários de votos → **B108**.
|
||||
- Voltar + skeleton do **Início** → **B106** (shared de rows ok).
|
||||
- Ranking/geo/continuity → B92–B94.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Sticky + teclado virtual (iOS) empurra visualViewport.** **Mitigação:** sticky simples v1; se critique mostrar jump, medir — não `visualViewport` polyfill neste appetite.
|
||||
- **Tornar todo passo “title-less”.** **Mitigação:** só município; outros passos mantêm pergunta no h1.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **`position: sticky` no dock do Início.** Só se produto pedir paridade (hoje a busca já está no fundo).
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #142
|
||||
- `src/components/campaign/shared/WizardMunicipalitySearchStep.tsx`
|
||||
- `src/components/campaign/shared/CampaignWizardShell.tsx`
|
||||
- `src/lib/campaignWizardCopy.ts`
|
||||
- [polimento-busca-municipio-wizard.md](polimento-busca-municipio-wizard.md) (B95) · [busca-municipio-wizard.md](busca-municipio-wizard.md) (B60) · [header-mobile-wizard-campanha.md](header-mobile-wizard-campanha.md) (B75)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
Loading…
Reference in a new issue