Merge pull request #134 from fsolla/cursor/plan-b101-b105-drawer-polish

docs: planos B101–B105 — polish mobile drawer/strip
This commit is contained in:
Francisco Solla 2026-08-01 14:51:44 -03:00 committed by GitHub
commit da7c813a55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 566 additions and 0 deletions

View file

@ -0,0 +1,137 @@
# Bottom drawer — handle, gesto, scroll, peek discreto, labels 2 linhas
Status: registrado
Atualizado em: 2026-08-01
Issue: #132
Priority: P1
Model: cursor-grok-4.5-medium
Impeccable: C — chrome `CampaignQuickActionsDrawer` (comportamento + geometria)
Appetite: ~1–1,5d eng; snap/gesto/scroll + peek busca + altura labels; sem migration
Responsável: —
## Design (Impeccable)
Âncoras: `PRODUCT.md` (thumb zone; Feel the action) / `DESIGN.md` · B79/B91/B100 · Base UI Drawer snap · tema `campaign`.
Na implementação (`work-issue`): craft compacto → critique → polish (viewport ~390; detalhe município; scroll real).
Brief compacto:
- **Persona / contexto:** CG/assessor no celular fora do Início; uma mão; alterna ler a página e disparar ação/busca.
- **Job principal:** (1) handle **no topo** do sheet; (2) puxar para baixo fecha/recolhe; (3) scroll página ↓ fecha, ↑ abre; (4) collapsed ainda mostra busca **sem placeholder**; (5) títulos das ações com **2 linhas** visíveis.
- **Estratégia de cor:** Restrained.
- **Edit where you see:** não — launchers.
- **Anti-goals:** scrim modal; FAB; bottom nav; redesenhar catálogos B80–B90; absorver crash da busca (→ **B102** primeiro ou em paralelo sem bloquear geometria).
### Wireframe (texto)
**Dock (aberto):**
```text
┌─ página ───────────────────────────────────────┐
│ conteúdo │
├─ drawer ───────────────────────────────────────┤
│ ═ handle (TOPO) │
│ [○][○][○]… labels em 2 linhas visíveis │
│ ┌ Buscar na campanha (placeholder ok) ───────┐ │
│ └────────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
```
**Collapsed (fechado / após scroll ↓):**
```text
┌─ página (mais conteúdo) ───────────────────────┐
│ … │
├────────────────────────────────────────────────┤
│ ═ handle │
│ ┌ (input sem placeholder — discreto) ────────┐ │
│ └────────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
```
**Gesto:** drag handle/sheet ↓ → collapsed; scroll content ↓ → collapsed; scroll content ↑ → dock.
## Dados → decisão → apresentação
Dados: N/A — chrome; hits = B48+ via provider existente.
## Contexto
**B100 ✓** (#114) definiu dock no load + collapse no scroll **para baixo** + reopen **só** tap/pull (explicitamente **sem** reopen no scroll-up). Follow-up `a056616d` reordenou o DOM para o handle ficar **em baixo** (“snap shows bottom band”) — produto agora rejeita: handle deve voltar ao **topo**; puxar ↓ deve recolher; scroll ↑ deve **abrir**. Collapsed deve manter a barra de busca visível **sem placeholder**. Labels `line-clamp-2` existem no botão, mas dock `12rem` + `overflow-hidden` + handle embaixo cortam a 2ª linha.
## Objetivos
- **Handle no topo** do `DrawerContent` (reverter ordem do `a056616d`); banda visível do snap mostra handle → ações → busca.
- **Swipe/drag para baixo** no drawer recolhe para collapsed (Base UI `swipeDirection="down"` + snap; validar que não fica “só tap”).
- **Scroll do `campaign-content-scroll`:** ↓ além do limiar → collapsed; ↑ (delta ou `scrollTop` caindo / direção) → dock. Não reabrir se busca `uiFocused` _(assumido)_.
- **Collapsed:** renderiza input de busca **visível**, `placeholder=""` (ou omitido); sem strip de ações (ou strip hidden). Tap no input / focus → dock + focus.
- **Altura do dock / overflow:** 2ª linha dos labels visível (subir `QUICK_ACTIONS_SNAP_DOCK` e/ou `overflow-y-auto` na região de ações; tirar clip que come `line-clamp-2`).
- Load / navegação: estado inicial = dock (B100).
- Pins: unit direção scroll; e2e mobile handle no topo + collapse swipe se estável.
- Sem migration / Consent.
- Tracer: load dock → scroll ↓ collapsed (busca sem placeholder) → scroll ↑ dock → labels 2 linhas no viewport.
## Decisões travadas
- **Supersede B100 no reopen por scroll-up e no collapsed “só handle”.** Fonte: produto 2026-08-01. **Rejeitado:** manter “reopen só tap” (B100); collapsed só handle sem busca.
- **Handle no topo — reverter o DOM order do `a056616d`.** Se a geometria do snap exigir `padding-bottom` Base UI, aplicar **sem** mover o handle para baixo. **Rejeitado:** handle embaixo como “fix” de banda visível.
- **Collapsed = handle + busca discreta (sem placeholder); ações hidden.** **Rejeitado:** collapsed = só handle (B100); esconder busca no collapsed.
- **Scroll-up reabre** com limiar de direção (delta ↑ ≥ N px) ou `scrollTop` caindo abaixo do limiar — craft escolhe o mais estável; pin no unit. **Rejeitado:** hysteresis multi-eixo complexa neste item.
- **i18n:** ids `dock`/`collapsed`; aria pt-BR; placeholder vazio no collapsed (label sr-only permanece).
## Questões em aberto
- **Limiar scroll-up para abrir?** **Opções:** A) espelhar 24 px de delta ↑ | B) `scrollTop < 24` (só perto do topo). **Recomendação:** **A** (simétrico ao collapse). _(assumido)_
- **Focus no input collapsed:** expande para dock automaticamente? **Opções:** A) sim | B) só digitar. **Recomendação:** **A**. _(assumido)_
## Abordagem proposta
```mermaid
flowchart TB
Scroll["campaign-content-scroll"] -->|delta down| Collapsed
Scroll -->|delta up| Dock
Handle["handle TOP"] -->|swipe down| Collapsed
Collapsed --> SearchPeek["search no placeholder"]
Dock --> Strip["actions 2-line labels"]
Dock --> SearchFull["search + placeholder"]
```
Componentes:
- **`CampaignQuickActionsDrawer.tsx`**: ordem handle → context; collapsed mostra `CampaignHomeSearch` (ou slot) sem placeholder; dock mostra strip + search completa; overflow/altura.
- **`CampaignQuickActionsHost.tsx` / snap context:** listener de scroll com direção (↑/↓); respeitar `uiFocused`.
- **`campaignQuickActionSnap.ts`:** recalibrar `QUICK_ACTIONS_SNAP_COLLAPSED` / `DOCK` para caber handle+busca (collapsed) e strip 2 linhas+busca (dock).
- **`CampaignHomeSearch` / `CampaignSearchInput`:** prop opcional `placeholder` / `discreet` para collapsed.
- **E2e / unit** atualizar expectativas B100 (scroll-up agora reabre; collapsed tem busca).
- **Migration:** Sem migration.
## Dependências
- Soft: B100 ✓, B91 ✓. Soft: **B101** (bleed/gap da strip — pode pousar junto). Soft: **B102** (crash) — se o crash bloquear teste manual da busca no drawer, priorizar B102.
- Nenhuma dura de Issue aberta.
## Não escopo
- Crash focus/type → **B102**.
- Gap/padding dos botões → **B101**.
- Título “Sugestões” → **B103**.
- Catálogos de ações por rota.
## Rabbit holes
- **Terceiro snap “expanded” para resultados.** Mitigação: Adiado; resultados rolam no dock ou a página.
- **`position: fixed` caseiro no lugar do Base UI.** Mitigação: só se swipe+snap forem irrecuperáveis após handle no topo + padding-bottom correto.
- **Persistir snap em sessionStorage.** Mitigação: fora; reset no pathname (B100).
## Adiado com gatilho
- Animação spring custom além do Base UI. Revisitar se critique pedir motion.
- Peek de 1 ícone de ação no collapsed. Revisitar se a mesa achar busca-só pobre.
## Referências
- GitHub Issue #132 (B105)
- `CampaignQuickActionsDrawer.tsx` · `CampaignQuickActionsHost.tsx` · `campaignQuickActionSnap.ts` · `Drawer.tsx` · `docs/plans/bottom-drawer-peek-acoes-busca.md` (B100 — supersedido em parte)
- Commit `a056616d` (handle bottom — reverter ordem)
- `PRODUCT.md` / `DESIGN.md` — Field Desk chrome

View file

@ -0,0 +1,117 @@
# Crash — busca no bottom drawer (focus / digitação)
Status: registrado
Atualizado em: 2026-08-01
Issue: #129 (file-miss #133)
Priority: P0
Model: cursor-grok-4.5-medium
Impeccable: B — `CampaignQuickActionsDrawer` + `CampaignGlobalSearchBody`
Appetite: ~0,5–1d eng; root-cause + fix + pin focus/type; sem migration
Responsável: —
## Design (Impeccable)
Âncoras: `PRODUCT.md` (Feel the action) · B91/B100 drawer search · tema `campaign`.
Na implementação: reproduzir → fix mínimo → pin (unit + e2e mobile se estável). Sem craft visual além do necessário para o ritual de busca voltar.
Brief:
- **Persona:** staff no detalhe de município (mobile); toca “Buscar na campanha” no drawer.
- **Job principal:** focar e digitar **sem** a página cair; ver suggest/hits.
- **Anti-goals:** redesenhar o drawer (→ **B105**); silenciar o throw com try/catch cosmético.
### Wireframe (texto)
```text
┌─ /campanha/municipios/foo (mobile) ────────────┐
│ conteúdo │
├─ drawer dock ──────────────────────────────────┤
│ ═ handle │
│ [ações…] │
│ ┌ Buscar na campanha ──── focus/type OK ─────┐ │
│ │ Sugestões / hits (sem crash) │ │
│ └────────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
```
## Dados → decisão → apresentação
Dados: N/A neste item (hits já existem via B48/B68/B91); só estabilidade do chrome.
## Contexto
Fora do Início, o drawer (**B79–B91**, **B100 ✓**) embute `CampaignGlobalSearchBody` sob `CampaignGlobalSearchProvider` em `CampaignAppScrollChrome`. Produto (2026-08-01): **ao clicar ou começar a escrever na barra de busca, a página crasha**.
Diagnóstico preliminar (código em `main`):
1. Unit do drawer (`campaignQuickActionsDrawer.unit.spec.tsx`) cobre `change` → POST `mode: 'search'`, **não** `focus` → `setInputFocused(true)` → `mode: 'suggest'`.
2. `useHomeSearchResultsState` entra em `suggestMode` quando `uiFocused && !query.isActive` e monta os grupos B48 dentro do snap (`overflow-hidden`, dock `12rem`).
3. `CampaignContentScrollWithPeek` chama `useHomeSearch()` — correto só dentro do provider; árvore atual amarra provider + peek. Hipótese secundária: interação Base UI Drawer non-modal + focus/teclado / nested `Drawer` dos action buttons.
4. Runtime Vercel (server) **não** mostra o digest do crash (erro de client).
**Miss de agente:** B100 / follow-up `a056616d` shipparam sem pin de focus+type no drawer. Registrar via `pnpm agent:file-miss` (`kind:defect` ou `agent-miss`) **neste delivery**, depois o fix.
## Objetivos
- Reproduzir o crash (mobile real ou Playwright viewport mobile em `/campanha/municipios/[slug]`).
- Eliminar a causa raiz (não engolir o erro).
- Pins: unit **focus** dispara suggest sem throw; unit/e2e **type** mantém search; regressão no caminho provider/`uiFocused`.
- Guardrails: sem migration; leader lockdown; Início intacto.
- Tracer: focus no input do drawer → região de resultados monta; digitar “ca” → POST search; zero uncaught.
## Decisões travadas
- **Issue própria P0 + file-miss, não absorver em B105.** Crash bloqueia o ritual. **Rejeitado:** “conserta no polish do drawer”.
- **Fix mínimo no caminho focus→suggest→render; não redesenhar snap.** Geometria/gesto → **B105**. **Rejeitado:** reescrever busca só para o drawer.
- **File-miss obrigatório** com causa + PR culpado quando conhecido. **Rejeitado:** só Issue de feature sem harvest.
- **i18n:** ids existentes; sem copy nova.
## Questões em aberto
- **Causa raiz exata antes do claim?** **Opções:** A) fechar no `work-issue` com repro primeiro | B) bipartir plan/exec. **Recomendação:** **A** — appetite cabe; hypothese já estreita. _(assumido)_
## Abordagem proposta
```mermaid
flowchart LR
Focus["input onFocus"] --> Ui["uiFocused"]
Ui --> Suggest["POST mode:suggest"]
Suggest --> Groups["HomeSearch*Group"]
Groups -->|crash?| Fix["fix root"]
Type["onChange"] --> Search["POST mode:search"]
```
Componentes:
- **Repro:** Playwright mobile ou unit com `fireEvent.focus` + wait suggest; ler stack.
- **Provável superfície:** `CampaignStaffGlobalSearch` / `HomeSearchResultsLayout` / `Drawer` + focus; eventualmente provider tree se achar caminho sem `HomeSearchProvider`.
- **Pins em** `tests/unit/campaignQuickActionsDrawer.unit.spec.tsx` (+ e2e `campaignMunicipalities` se estável).
- **Migration:** Sem migration.
- **File-miss:** `pnpm agent:file-miss -- --title "…" --kind defect` com body apontando B100/`a056616d` e ausência de pin focus.
## Dependências
- Soft: B91 ✓, B100 ✓. Nenhuma dura. **B105** pode pousar depois (gesto/handle); não bloqueia o fix.
## Não escopo
- Handle no topo / scroll abre / placeholder collapsed → **B105**.
- Remover título “Sugestões” → **B103** (pode coincidir visualmente no empty, mas é copy).
- Strip bleed → **B101**.
## Rabbit holes
- **Trocar Base UI Drawer por sheet caseiro.** Mitigação: só se a root cause for o primitivo e não houver escape hatch documentado.
- **Duplicar `CampaignHomeSearch` “safe” só para drawer.** Mitigação: consertar o shared.
## Adiado com gatilho
- 3º snap alto só para resultados. Revisitar se, após o fix, critique de **B105** mostrar hits cortados no dock.
## Referências
- GitHub Issue #129 (B102) · file-miss #133
- `CampaignQuickActionsDrawer.tsx` · `CampaignAppScrollChrome.tsx` · `CampaignQuickActionsHost.tsx` · `HomeSearchResultsContext.tsx` · `CampaignStaffGlobalSearch.tsx`
- `tests/unit/campaignQuickActionsDrawer.unit.spec.tsx`
- AGENTS.md — Feel the action; client boundary

View file

@ -0,0 +1,100 @@
# Remover título “Sugestões” no empty state da busca
Status: registrado
Atualizado em: 2026-08-01
Issue: #130
Priority: P2
Model: composer-2.5
Impeccable: B — `HomeSearchMunicipalityGroup` (modo suggest)
Appetite: ~0,25d eng; copy/CSS; sem migration
Responsável: —
## Design (Impeccable)
Âncoras: `PRODUCT.md` (Clarity under pressure) · B68 suggest · tema `campaign`.
Na implementação: craft mínimo → critique → polish (Início focus vazio).
Brief:
- **Persona:** staff foca a busca no Início; já vê a lista curada.
- **Job principal:** ir aos hits sem cabeçalho decorativo.
- **Anti-goals:** remover o grupo “Municípios” no modo search; esconder `aria-label` acessível.
### Wireframe (texto)
```text
Antes (focus, query vazia):
Sugestões ← h2 + linha/espaço
────────────────
Cairu …
…
Depois:
Cairu …
…
```
## Dados → decisão → apresentação
Dados: N/A — só chrome do empty; ranking B68 intacto.
## Contexto
**B68 ✓** mostra seção com `sectionTitle = resultKind === 'suggest' ? 'Sugestões' : 'Municípios'` e `h2` via `HOME_SEARCH_GROUP_HEADING_CLASS` em `HomeSearchMunicipalityGroup`. Produto (2026-08-01): no empty/suggest do Início, o título **“Sugestões”** e a linha/espaço abaixo **não adicionam valor** — só ocupam thumb zone.
## Objetivos
- No `resultKind === 'suggest'`: **não renderizar** o `h2` “Sugestões” (nem regra visual equivalente).
- Manter `aria-label` no `<section>` (ou no list) para leitores de tela — “Sugestões” pode ficar só no accessible name.
- Modo `search`: título **“Municípios”** permanece _(assumido — pedido só citou suggest)_.
- Atualizar unit `homeSearchUi` / group spec se pinarem o heading.
- Sem migration.
## Decisões travadas
- **Só sumir o heading visual no suggest; hits B68 ficam.** **Rejeitado:** matar o empty state inteiro; renomear para outro título.
- **A11y: nome acessível sem `h2` visível.** **Rejeitado:** section muda sem nome.
- **i18n:** string “Sugestões” some da UI visível; id `sectionTitle` / `aria-label` ok.
## Questões em aberto
- **Modo search também perde “Municípios”?** **Opções:** A) não — só suggest | B) sim, todos os grupos sem h2. **Recomendação:** **A** (pedido explícito). _(assumido)_
## Abordagem proposta
```mermaid
flowchart LR
Group["HomeSearchMunicipalityGroup"] --> Kind{"resultKind"}
Kind -->|suggest| NoH2["section aria-label only"]
Kind -->|search| H2["h2 Municípios"]
```
Componentes:
- **`HomeSearchMunicipalityGroup.tsx`**: condicional no `h2`; `aria-label` sempre.
- **Tests** que assertam o texto “Sugestões” visível → ajustar.
- **Migration:** Sem migration.
## Dependências
- Soft: B68 ✓. Nenhuma dura.
## Não escopo
- Outros group headings (Lideranças, …) no modo search.
- Crash drawer → **B102**.
## Rabbit holes
- **Restyling de todos os group headings da busca.** Mitigação: um `if` no suggest.
## Adiado com gatilho
- Remover headings em todos os grupos no mobile. Revisitar se critique da busca pedir lista “flat”.
## Referências
- GitHub Issue #130 (B103)
- `HomeSearchMunicipalityGroup.tsx` · `src/lib/homeSearchUi.ts` · `docs/plans/sugestoes-busca-vazia-inicio.md` (B68)
- `PRODUCT.md` — Clarity under pressure

View file

@ -0,0 +1,109 @@
# Strip de ações — bleed sem faixa branca + hit area via padding
Status: registrado
Atualizado em: 2026-08-01
Issue: #128
Priority: P1
Model: composer-2.5
Impeccable: B — `CampaignHomeActionStrip` / `CampaignHomeActionButton` (Início + drawer)
Appetite: ~0,5d eng; CSS + pin de classes; sem migration
Responsável: —
## Design (Impeccable)
Âncoras: `PRODUCT.md` / `DESIGN.md` · B44/B67/B99 strip · tema `campaign`.
Na implementação (`work-issue`): craft compacto → critique → polish (iPhone estreito; Início **e** drawer).
Brief:
- **Persona / contexto:** assessor/CG no mobile; polegar na strip.
- **Job principal:** strip visualmente edge-to-edge (sem “trilho” branco lateral), primeiro botão **não** colado na borda, espaçamento entre ações apertado com área clicável generosa.
- **Estratégia de cor:** Restrained.
- **Edit where you see:** não — launchers.
- **Anti-goals:** encolher hit &lt;44 px; quebrar pan B67; `p-0` global na página; segundo componente de strip.
### Wireframe (texto)
```text
┌─ viewport ─────────────────────────────────────┐
│████ strip full-bleed (bg até a borda) ████████│
│ ┌ px interno ┐ ○ ○ ○ ○ →→ │
│ │ │ labels │
│ └────────────┘ (gap ul = 0; espaço = padding │
│ horizontal do botão) │
└────────────────────────────────────────────────┘
Mesmo contrato no Início e no drawer (B105).
```
## Dados → decisão → apresentação
Dados: N/A — chrome de launcher.
## Contexto
**B99 ✓** (#107) entregou `-mx-4` no slot `home-actions` + `gap-2` no `<ul>`. Feedback 2026-08-01: ainda há faixa branca lateral (pai `p-4` / `px-4` do drawer); primeiro botão cortado ou mal nascido; gap ainda folgado. Pedido explícito: **tirar o gap**; se o botão tiver margin, virar **padding** — a faixa transparente continua clicável (hit area maior). O fix é **geral** (Início + `CampaignQuickActionsDrawer`).
## Objetivos
- Strip full-bleed no mobile: sem gutter branco entre borda da tela e o scroller; **padding-inline interno** no scroller/`ul` para o primeiro/último botão não colar na curved edge.
- `gap-2` → **`gap-0`**; espaçamento horizontal só via `padding-inline` (ou `px-*`) no `CampaignHomeActionButton` / `li` — hit target cresce sem afastar os círculos visualmente demais.
- Manter: pan touch + drag fine (B67), scrollbar oculta, `line-clamp-2`, largura do círculo B58.
- Drawer: remover/`compensar` o `px-4` do `#quickActionContext` **só em torno da strip** (busca pode manter gutter) — ou bleed da strip com `-mx-4` + `px-4` interno, espelhando o Início.
- Guardrails: sem migration / Consent / action; pin unit das classes se já houver spec de layout.
- Tracer: screenshot/e2e leve Início + drawer — strip cola na borda; primeiro botão tem inset &gt;0.
## Decisões travadas
- **Item novo (B101), não reabrir B99.** Continuação do aperto + hit area. **Rejeitado:** editar só o as-built B99 sem Issue.
- **Espaço entre ações = padding do controle, não `gap`/`margin` no `ul`.** Fonte: produto 2026-08-01. **Rejeitado:** só baixar `gap-2`→`gap-1` (não resolve hit area transparente); margin no `li` (mata a área morta clicável).
- **Bleed da strip, não `p-0` no scroll da página / drawer inteiro.** Busca e conteúdo mantêm gutter. **Rejeitado:** zerar padding do `#quickActionContext` inteiro.
- **i18n:** ids `CampaignHomeActionStrip` / `actionControlClassName`; copy intacta.
## Questões em aberto
- **Quanto de `padding-inline` no botão?** **Opções:** A) `px-1.5` (~6 px) | B) `px-2` (8 px) | C) só `pl/pr` no primeiro/último via pseudo. **Recomendação:** **B** no craft; critique se os círculos parecerem colados. _(assumido)_
## Abordagem proposta
```mermaid
flowchart TB
Home["home-actions -mx-4"] --> Strip["CampaignHomeActionStrip"]
Drawer["quickActionContext"] -->|"-mx-4 na strip"| Strip
Strip --> Ul["ul gap-0 + px-4 interno"]
Ul --> Btn["ActionButton px-* (hit)"]
```
Componentes:
- **`CampaignHomeActionStrip.tsx`**: `gap-2` → `gap-0`; padding horizontal no scroller ou `ul` (compensar bleed).
- **`CampaignHomeActionButton.tsx`**: acrescentar `px-*` em `actionControlClassName` (não margin).
- **`CampaignHomeLayout` / drawer**: garantir bleed + inset interno nos dois call sites; drawer deixa de “prender” a strip com `px-4` sem bleed.
- **Unit:** atualizar pin de classes se existir; caso contrário assert mínimo no strip spec.
- **Migration:** Sem migration.
## Dependências
- Soft: B99 ✓, B67 ✓. Soft com **B105** (altura do dock / 2ª linha do label — geometria do drawer, não spacing).
- Nenhuma dura.
## Não escopo
- Ritual scroll/handle/placeholder do drawer → **B105**.
- Copy “Pular” → **B104**.
- Crash da busca → **B102**.
- Remover título “Sugestões” → **B103**.
## Rabbit holes
- **Reescrever strip com CSS scroll-padding / snap complexos.** Mitigação: só gap/padding/bleed.
- **Componente `DrawerActionStrip` paralelo.** Mitigação: um strip, dois call sites.
## Adiado com gatilho
- Fade edges nas pontas do pan. Revisitar se critique mostrar que o bleed “corta” o último círculo sem affordance de overflow.
## Referências
- GitHub Issue #128 (B101) · #107 (B99 ✓) · `CampaignHomeActionStrip.tsx` · `CampaignHomeActionButton.tsx` · `CampaignHomeLayout.tsx` · `CampaignQuickActionsDrawer.tsx`
- `PRODUCT.md` / `DESIGN.md` — thumb zone / Clarity under pressure

View file

@ -0,0 +1,103 @@
# Wizard — “Pular” curto nas ações encadeadas
Status: registrado
Atualizado em: 2026-08-01
Issue: #131
Priority: P1
Model: composer-2.5
Impeccable: B — slot direito do header mobile do wizard
Appetite: ~0,25–0,5d eng; constantes de skip + pins; sem migration
Responsável: —
## Design (Impeccable)
Âncoras: `PRODUCT.md` · B75/B96 header · tema `campaign` (Mandate Red).
Na implementação: craft compacto → critique → polish (header estreito).
Brief:
- **Persona:** staff no meio da cadeia pós-ação principal (B98).
- **Job principal:** pular o ajuste opcional com um verbo curto; na ação principal, **X** continua.
- **Anti-goals:** X+Pular juntos; “Pular …” longo no header.
### Wireframe (texto)
```text
Principal (standalone / 1ª config):
┌─ header ──────────────────────────────┐
│ [Voltar?] Ajustar votos · Cairu [X] │
└───────────────────────────────────────┘
Encadeada (ex. tendência após votos):
┌─ header ──────────────────────────────┐
│ [Voltar] Mudar tendência · Cairu [Pular] │
└───────────────────────────────────────┘
```
## Dados → decisão → apresentação
Dados: N/A — chrome de fluxo.
## Contexto
**B96 ✓** (#104) alinhou: skip só quando o passo é subfluxo encadeado; label **específica** (“Pular mudança de tendência →”, etc.) — B75 pedia o nome do fluxo. **B98 ✓** encadeia os ajustes. Produto (2026-08-01): nas encadeadas, o direito deve ser só **“Pular”**; a ação principal mantém **X**.
Constantes atuais: `WIZARD_*_SKIP_LABEL` em `campaignWizardCopy.ts`, `wizardSignalUi.ts`, `politicalTrendWizardUi.ts`.
## Objetivos
- Toda label de skip encadeada → **`"Pular"`** (constante única compartilhada, idealmente).
- Resolvers (`resolveWizard*Skip`) intactos na **condição** (quando há skip); só a string muda.
- Ação principal / standalone: `skip` undefined → header mostra **X** (B96).
- Unit: labels === `'Pular'`; standalone tendência/sinal sem skip.
- Sem migration.
## Decisões travadas
- **Supersede B96 na copy: label genérica “Pular”.** Fonte: produto 2026-08-01. **Rejeitado:** manter nome do fluxo (B96/B75 — supersedido neste ponto); “Pular →”.
- **Uma constante compartilhada** (`WIZARD_CHAIN_SKIP_LABEL = 'Pular'`) reusada pelos quatro fluxos. **Rejeitado:** quatro strings iguais espalhadas.
- **Destino do skip / cadeia = B98 (já entregue); este item é só copy.** **Rejeitado:** reabrir matriz de encadeamento.
- **i18n:** id `WIZARD_CHAIN_SKIP_LABEL`; copy pt-BR “Pular”.
## Questões em aberto
- **Seta “→” some?** **Opções:** A) sim, só “Pular” | B) “Pular →”. **Recomendação:** **A**. _(assumido)_
## Abordagem proposta
```mermaid
flowchart LR
Resolve["resolveWizard*Skip"] -->|encadeado| Label["Pular"]
Resolve -->|standalone| X["skip undefined → X"]
```
Componentes:
- **`src/lib/campaignWizardCopy.ts`** (ou módulo de contrato): export `WIZARD_CHAIN_SKIP_LABEL`.
- **`wizardSignalUi.ts` / `politicalTrendWizardUi.ts` / votes / leadership:** apontar label para a constante; remover strings longas se knip reclamar.
- **Unit** dos resolvers + qualquer snapshot de header.
- **Migration:** Sem migration.
## Dependências
- Soft: B96 ✓, B98 ✓. Nenhuma dura.
## Não escopo
- Mudar quando o skip aparece (já B96/B98).
- Desktop copy fora do header mobile.
## Rabbit holes
- **Renomear todo o chrome B75.** Mitigação: só as quatro labels de skip.
## Adiado com gatilho
- Tooltip no “Pular” com o nome do subfluxo. Revisitar se a mesa achar o verbo ambíguo na cadeia longa.
## Referências
- GitHub Issue #131 (B104)
- `docs/plans/wizard-header-x-vs-pular.md` (B96) · `campaignWizardCopy.ts` · `wizardSignalUi.ts` · `politicalTrendWizardUi.ts` · `wizardLeadershipContract.ts`
- AGENTS.md — naming / pt-BR copy