mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
Merge pull request #101 from fsolla/cursor/plan-wizard-deploy-ops-1367
OPS11 + B95–B99 — planos (deploy cooldown, wizard, strip)
This commit is contained in:
commit
e47eef07af
6 changed files with 698 additions and 0 deletions
120
docs/plans/bug-salvar-tendencia-fecha-wizard.md
Normal file
120
docs/plans/bug-salvar-tendencia-fecha-wizard.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Bug — Salvar tendência fecha o wizard (não volta ao começo)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P0
|
||||
Model: composer-2.5
|
||||
Impeccable: B — pós-submit de `WizardTrendNoteStep`
|
||||
Appetite: ~0,5d eng; race revalidate×redirect; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Feel the action; commit óbvio) · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish (toast + navegação).
|
||||
|
||||
Brief:
|
||||
|
||||
- **Persona:** CG salvou a justificativa de tendência.
|
||||
- **Job principal:** sair do ritual com feedback de sucesso — **não** reabrir a grade de tendências.
|
||||
- **Anti-goals:** loop escolha↔nota; sumir sem toast.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
Antes (bug):
|
||||
Nota → [Salvar] → (revalidate) página vê status==atual → redirect escolha
|
||||
→ “volta ao começo”
|
||||
|
||||
Depois:
|
||||
Nota → [Salvar] → toast “Tendência política registrada.” → Início
|
||||
(ou, com B98: próximo encadeado / tela de sucesso)
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — navegação pós-write; mensagem de toast já existe (`WIZARD_TREND_SAVED_MESSAGE`).
|
||||
|
||||
## Contexto
|
||||
|
||||
`WizardTrendNoteStep` grava via `setMunicipalityPoliticalTrendFormAction` e, no sucesso client, `useCampaignFormSuccessToast` → `router.push(CAMPAIGN_HOME)`.
|
||||
|
||||
A action também chama `revalidateMunicipalityListPaths`. A página RSC (`acoes/[slug]/page.tsx`) para `change-trend`, quando `trendStatus` query **igual** ao `currentStatus` do município (já persistido), faz `redirect(...)` **de volta à etapa de escolha** (sem `tendencia=`).
|
||||
|
||||
Ordem típica do bug:
|
||||
|
||||
1. Usuário salva nota com `?tendencia=favoravel` (atual era outra).
|
||||
2. Write OK + revalidate; RSC refresha com `politicalTrend.status === favoravel`.
|
||||
3. Branch `trendStatus === currentStatus` → **redirect para a escolha**.
|
||||
4. Client `router.push(HOME)` perde a corrida ou é sobrescrito → sensação de “Salvar volta ao começo”.
|
||||
|
||||
Pedido (2026-08-01): Salvar **fecha** o wizard.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Após Salvar bem-sucedido na nota de tendência, o usuário **não** permanece/reentra na grade de escolha do mesmo município.
|
||||
- Destino v1: **Início** (toast intacto). Quando **B98** existir, o destino pode ser o próximo da cadeia — este item deixa um único ponto de navegação pós-sucesso.
|
||||
- Eliminar a corrida revalidate → `redirect` de “status já atual” enquanto o client ainda está no passo nota **ou** tornar o redirect inofensivo (não mandar de volta à escolha após save).
|
||||
- Unit/int ou e2e smoke: save → URL final ≠ escolha do mesmo fluxo.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Fechar = navegar para fora do passo nota com sucesso explícito (Início v1).** Fonte: produto 2026-08-01. **Rejeitado:** ficar na nota com banner; voltar à escolha “para confirmar”.
|
||||
- **Corrigir a race no server redirect e/ou no success path — não só `router.push` mais agressivo.** O `redirect` quando `trendStatus === currentStatus` é legítimo para deep-link stale **antes** do save; pós-save ele vira armadilha. **Rejeitado:** só `router.replace(HOME)` sem mudar o redirect (continua flash/loop em rede lenta).
|
||||
- **Preferência de implementação (barata):** success path da form action (ou wrapper do wizard) usa `redirect(CAMPAIGN_HOME)` server-side **ou** a página deixa de redirecionar à escolha quando o doc já reflete o query (tratar como “já aplicado” → home / success). **Recomendação no craft:** `runCampaignRedirectFormAction` / redirect no success do wizard trend (precedente outros forms) **ou** client navigation + não revalidar a rota do wizard antes do push. Pin a opção no as-built.
|
||||
- **i18n:** `WIZARD_TREND_SAVED_MESSAGE` intacto.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Redirect server na action vs client toast+push?** **Opções:** A `redirect` na action (fecha cedo; toast via query `?saved=1`) | B manter toast client mas impedir redirect RSC à escolha (ex. não revalidate path do wizard; ou branch “same status” → home). **Recomendação:** **B com revalidate seletivo + push**, ou A se B ainda correr — Feel the action prefere toast; redirect puro sem toast é pior. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Save["POST trend note"] --> Write["setMunicipalityPoliticalTrend"]
|
||||
Write --> Reval["revalidate listas"]
|
||||
Write --> Success["status success"]
|
||||
Success --> Nav["navigate HOME (sem passar pela escolha)"]
|
||||
Page["RSC change-trend"] -->|"stale deep-link only"| Choice["redirect escolha"]
|
||||
Page -->|"post-save same status"| NotChoice["não empurrar escolha"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`WizardTrendNoteStep.tsx`** + **`municipalityStaffFormActions.ts`** / página `acoes/[slug]`: quebrar a corrida (detalhe no craft).
|
||||
- **`acoes/[slug]/page.tsx`**: revisitar branch `trendStatus === currentStatus` — distinguir deep-link inválido vs “já salvo”.
|
||||
- **Teste:** unit do helper de resolução de destino; e2e smoke se barato.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura. Soft: B64 ✓. Compatível com **B96** (X no standalone). Destino encadeado futuro: **B98**.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Encadear tendência após votos → **B98**.
|
||||
- Mudar tiles/nota UI.
|
||||
- Autosave da lista (B24) — outro path.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Optimistic UI da tendência no wizard.** **Mitigação:** fix de navegação só.
|
||||
- **Remover o guard `trendStatus === currentStatus` por completo.** Pode reabrir deep-links lixo; mitigar com destino home ou mensagem, não loop.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Pós-sucesso → próximo da cadeia.** **B98**.
|
||||
|
||||
## Referências
|
||||
|
||||
- `src/components/campaign/municipality/WizardTrendNoteStep.tsx`
|
||||
- `src/app/(campaign)/campanha/(app)/acoes/[slug]/page.tsx` (branch change-trend)
|
||||
- `src/app/(campaign)/campanha/(app)/municipios/municipalityStaffFormActions.ts`
|
||||
- `src/components/campaign/shared/useCampaignFormSuccessToast.ts`
|
||||
- [wizard-mudar-tendencia.md](wizard-mudar-tendencia.md)
|
||||
- AGENTS.md — Feel the action / form actions
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
99
docs/plans/ci-deploy-cooldown-vercel.md
Normal file
99
docs/plans/ci-deploy-cooldown-vercel.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# CI — cooldown mínimo de 30 min entre deploys de produção (Vercel)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P0
|
||||
Model: composer-2.5
|
||||
Impeccable: A — N/A (sem superfície UI)
|
||||
Appetite: ~0,5–1d eng; 1 job step em `ci.yml` + retry/requeue; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — gate de infra/CI; sem KPI de produto.
|
||||
|
||||
## Contexto
|
||||
|
||||
O verificador `ci.yml` (push em `main`) sobe produção com `vercel deploy --prebuilt --prod`. No plano Hobby/Free da Vercel o upload da CLI estoura o teto diário (`api-upload-free`, “Too many requests — try again in 24 hours / more than 5000”). Em 2026-08-01 vários merges em sequência (incl. **B92–B94**) passaram nos checks e **falharam no job `deploy`** — o código ficou em `main` sem ir a produção.
|
||||
|
||||
Já existe um “stale HEAD” guard (só o tip de `main` faz deploy) + `requeue` se `main` avançou. **Não** há intervalo mínimo entre deploys bem-sucedidos: cada merge verde dispara upload completo.
|
||||
|
||||
Pedido de produto (2026-08-01): **mínimo 30 minutos** entre um deploy e o próximo; antes de continuar, o job verifica a idade do último deploy.
|
||||
|
||||
**Consequência imediata (não é bug de UI):** o empty/suggest do wizard de município (**B92–B94**, Issues #92–#94, merged) **não está em produção** porque o deploy falhou por rate limit — não porque o idle esteja “quebrado” no código de `main`.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Antes de `vercel pull`/`build`/`deploy --prod`, consultar o **último deployment de produção** do projeto e, se a idade for **< 30 min**, **não** fazer upload agora.
|
||||
- Em skip por cooldown: sair com sucesso “adiado” (não vermelho falso) e **reagendar** o workflow (mesmo espírito do `requeue` atual) para rodar depois do restante do intervalo — assim o tip de `main` ainda chega a produção sem depender de humano.
|
||||
- Opcional barato no mesmo PR: passar `--archive=tgz` no `vercel deploy` (a própria CLI sugere) para reduzir pressão de upload — **não** substitui o cooldown.
|
||||
- Guardrails: sem migration; secrets `VERCEL_*` já usados; não tocar `ci-pr.yml` (sem deploy); Vercel preview Git continua ignorado (`vercel-ignore-build.sh`).
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Fonte da idade = último production deployment do projeto Vercel (API/`vercel ls`/`vercel inspect`), não o last successful `ci.yml` run.** Motivo: o teto é da conta Vercel; um deploy manual/CLI fora do Actions também conta. **Rejeitado:** só olhar `gh run list --workflow=ci.yml` (ignora deploys fora do GHA); sleep fixo de 30 min em todo run (desperdício quando já passou).
|
||||
- **Cooldown = 30 min (constante nomeada no workflow/script).** Fonte: pedido 2026-08-01. **Rejeitado:** 1 h (atrasa demais o tip); só backoff após 429 (já estouramos o dia — tarde demais).
|
||||
- **Skip por cooldown ≠ failure.** Job `deploy` marca “adiado”, `requeue`/dispatch agenda novo `ci.yml` com delay (ou workflow auxiliar `workflow_dispatch` + `wait`). **Rejeitado:** `exit 1` (alarme falso; agentes/CI “dono do PR” interpretam vermelho); skip silencioso sem requeue (main fica sem deploy até o próximo merge).
|
||||
- **Stale HEAD continua primeiro.** Ordem: tip check → cooldown check → credentials → pull/build/deploy. **Rejeitado:** cooldown antes do tip (adiaria deploy que já seria skipado).
|
||||
- **i18n:** ids em inglês (`DEPLOY_COOLDOWN_MS`, `lastProductionDeployAt`); logs pt-BR ou EN ok no Actions.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Como obter o timestamp do último prod deploy?** **Opções:** A) `vercel ls <project> --prod` / API REST deployments filtrando `target=production` + `ready` | B) gravar timestamp em Actions cache/artifact após deploy OK | C) GitHub Deployment API. **Recomendação:** **A** — fonte de verdade Vercel; B desincroniza se houver deploy fora do CI. _(assumido)_
|
||||
- **Delay do requeue: `gh workflow run` imediato + step `sleep` residual, ou cron/`repository_dispatch`?** **Opções:** A sleep no mesmo job até completar 30 min (bloqueia runner) | B dispatch com `sleep` curto só do residual num job leve | C Actions `schedule` a cada 15 min checando tip≠prod. **Recomendação:** **B** — job leve espera o residual (cap ~30 min) e re-dispara `ci.yml`; A gasta minutos caros no job de build. _(assumido — validar custo de runner)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Tip["Guard stale HEAD"] -->|tip| Age["Idade último prod deploy"]
|
||||
Age -->|"< 30 min"| Wait["Skip + schedule requeue"]
|
||||
Age -->|">= 30 min"| Creds["Require VERCEL_*"]
|
||||
Creds --> Build["pull + build + deploy --prod --archive=tgz"]
|
||||
Build --> Ok["Record / log deploy ok"]
|
||||
Wait --> Later["ci.yml again after residual"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`.github/workflows/ci.yml` (job `deploy`)**: step `Check deploy cooldown` após tip; output `cooldown=true|false` + `wait_seconds`; condicionar steps de build/deploy; estender `requeue` (ou step irmão) para “main tip sem deploy recente / cooldown”.
|
||||
- **Script pequeno** (`scripts/vercel-deploy-cooldown.mjs` ou shell no step): chama API Vercel com `VERCEL_TOKEN` + `VERCEL_PROJECT_ID`; devolve idade; constante `DEPLOY_COOLDOWN_MS = 30 * 60 * 1000`.
|
||||
- **`vercel deploy --prebuilt --prod --archive=tgz`**: redução de arquivos no upload (hint da CLI no erro 2026-08-01).
|
||||
- **Verificação pós-entrega:** após um deploy OK, smoke do idle do wizard (`mode: wizard-municipality-suggest`) em produção — confirma que B92–B94 saíram do limbo.
|
||||
- **Migration:** Sem migration, sem collection, sem server action de app.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma Issue de produto. Soft: entendimento do `requeue` atual em `ci.yml`; secrets Vercel já no repo.
|
||||
- Desbloqueia visualização em prod de tudo que está em `main` sem deploy (incl. B92–B94).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Upgrade de plano Vercel / mudança de limites da conta.
|
||||
- Deploy de preview por branch (já skipado).
|
||||
- Mudar gate de merge (`checks` / `migration-lock`).
|
||||
- Refatorar o orquestrador do agent-pool para “batch merges” (adiado).
|
||||
- Bugfix de UI do empty state — causa raiz = undeployed; se após deploy OK o idle ainda falhar, abrir Issue B nova com evidência.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **“Já que toco deploy, migro para OIDC / Vercel Git / multi-env.”** Explode appetite. **Mitigação:** só cooldown + archive + requeue.
|
||||
- **State machine de fila de deploys no Postgres.** **Mitigação:** workflow + API Vercel bastam.
|
||||
- **Deduplicar builds com cache Vercel remoto exótico.** **Mitigação:** fora; `--prebuilt` já isola build→upload.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Batch de merges → um deploy.** Revisitar se, com cooldown de 30 min, o tip ainda atrasar >1 h sob carga do pool **e** produto aceitar “deploy só a cada N merges”.
|
||||
- **`--archive=tgz` sozinho sem cooldown.** Se o teto 5000/dia continuar após archive, o cooldown permanece necessário.
|
||||
|
||||
## Referências
|
||||
|
||||
- `.github/workflows/ci.yml` — jobs `deploy` / `requeue`, tip guard
|
||||
- Erro observado 2026-08-01: `Too many requests … code: "api-upload-free"` no merge de B94 (#100)
|
||||
- Issues B92–B94 (#92–#94) — código em `main`, deploy falhou
|
||||
- `scripts/vercel-ignore-build.sh` — previews Git off
|
||||
- `docs/AGENT-OPS.md` — entrega = merge em `main`; deploy gated por `ci.yml`
|
||||
- AGENTS.md — região `gru1`; deploy via Actions
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
134
docs/plans/encadear-ajustes-wizard.md
Normal file
134
docs/plans/encadear-ajustes-wizard.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# Encadear ajustes após a ação principal do wizard
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P1
|
||||
Model: cursor-grok-4.5-medium
|
||||
Impeccable: C — orquestração multi-fluxo no chassis `/campanha/acoes`
|
||||
Appetite: ~1–2d eng; matriz de cadeia + navegação pós-sucesso; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Ação → Local → Quem; Continuity) / `DESIGN.md` · `fluxos-acao-primeiro-inicio.md` · B59/B75 chassis · tema `campaign`.
|
||||
|
||||
Na implementação: shape → craft → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** CG no polegar; acabou o ajuste principal (ex. votos) e ainda tem contexto do município na cabeça.
|
||||
- **Job principal:** oferecer **na sequência** os ajustes que costumam vir juntos, sem voltar ao Início e reescolher município; poder **Pular** cada um.
|
||||
- **Estratégia de cor:** Restrained; reusa tiles/passos já existentes.
|
||||
- **Edit where you see:** writes reusam actions existentes (não segundo sistema).
|
||||
- **Anti-goals:** state machine XState global; forçar todos os ajustes; reescolher município entre elos; resumo mega-commit (v1 pode gravar por elo como hoje).
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
Ajustar votos (entry)
|
||||
1. Busca município
|
||||
2. Config principal: votos → Salvar
|
||||
3. Encadeada: tendência? [Pular] … Salvar
|
||||
4. Encadeada: sinal? [Pular] … Salvar
|
||||
5. (opcional) liderança? [Pular]
|
||||
→ Início / “Atualização registrada em {município}”
|
||||
|
||||
Mudar tendência (entry)
|
||||
1. Município → 2. tendência (X até salvar) → 3. encadeadas leves (sinal?) → fim
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A como métrica nova — encadeia writes já existentes (votos, tendência, sinal, liderança). Decisão = “registrar agora o próximo ajuste neste município ou pular”.
|
||||
|
||||
## Contexto
|
||||
|
||||
O rascunho UX-1 ([fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md)) pediu continuidade no fim de A1 (“Quer também…?: sinal / tendência / liderança”). Os wizards standalone existem (votos, sinal, tendência, liderança), com `entryAction` query **já** usada para skip (sinal/liderança) e prefill de nota de tendência — mas **ninguém navega** de um fluxo ao próximo após o save.
|
||||
|
||||
`WizardExpectedVotesStep` tem fase `saved` com placeholder: _“Próximo passo deste fluxo (sinal, tendência ou resumo) em breve.”_ — gancho explícito.
|
||||
|
||||
Pedido (2026-08-01): ao iniciar uma ação → município → **ajuste principal** → **encadear** outros ajustes que façam sentido (ex. votos → tendência).
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Matriz v1 (travada abaixo) de `entryAction` → fila ordenada de subfluxos após sucesso da principal.
|
||||
- Após Salvar da principal: ir ao **primeiro** elo da fila **no mesmo município** (URL do wizard alvo + `entryAction` + `municipio=`), não ao Início.
|
||||
- Cada elo encadeado: chrome **Pular** (B96) → próximo elo ou fim; Salvar → próximo / fim.
|
||||
- Fim: Início (ou tela curta de sucesso reusando copy de votos) **sem** re-pedir município.
|
||||
- Guardrails: mesmas actions/access; sem migration; leader fora (layout `acoes` já gateia staff).
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Cadeia = navegação entre rotas/passos existentes + `entryAction`, não um mega-form único.** Reusa B61/B63/B64/B70. **Rejeitado:** um só POST transacional de tudo (caro; quebra skip parcial); checkboxes “Quer também?” sem subfluxo (raso demais vs tiles já feitos).
|
||||
- **Matriz v1 (assumida — validar com produto):**
|
||||
|
||||
| Entry (botão Início) | Principal | Encadeadas (ordem) |
|
||||
| -------------------- | --------- | ----------------------------- |
|
||||
| `update-votes` | votos | tendência → sinal → liderança |
|
||||
| `register-signal` | sinal | tendência |
|
||||
| `change-trend` | tendência | sinal |
|
||||
| `update-leadership` | liderança | — (sem cadeia v1) |
|
||||
|
||||
**Rejeitado:** incluir nível de envolvimento (E14) / assessor na v1 (A7/A8 do UX-1 — raros no ritual 7h); cadeia simétrica completa em todo entry.
|
||||
|
||||
- **Pular / Salvar avançam a fila; X só na principal (B96).** Encadeada nunca usa X como “pular um elo” — X abortaria o ritual inteiro só se quisermos dismiss global; **recomendação:** nas encadeadas dismiss/skip = próximo ou fim (Pular), sem X que descarte a principal já salva sem aviso. **Rejeitado:** X nas encadeadas = Início (ok como dismiss explícito se B75 mantiver — produto pediu Pular no lugar do X).
|
||||
- **Gravação por elo (já existente), não commit único no fim.** **Rejeitado:** rascunho multi-collection até o CTA final (appetite + U4).
|
||||
- **i18n:** `wizardChain`, `entryAction`, `nextChainHref`; copy pt-BR nos títulos de fluxo já do catálogo.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Sinal antes ou depois de tendência na cadeia de votos?** **Opções:** A tendência→sinal (tabela acima) | B sinal→tendência (UX-1 A1 passos 5–6). **Recomendação:** **A** — tendência é leitura política rápida; sinal é texto. _(assumido — validar)_
|
||||
- **Tela “Votos atualizados” intermediária some?** **Opções:** A pular direto à 1ª encadeada | B 1 toque “Continuar” na fase saved. **Recomendação:** **A** — menos atrito; Feel the action. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Entry["entryAction"] --> Main["passo principal"]
|
||||
Main -->|save OK| Queue["wizardChainQueue(entry)"]
|
||||
Queue --> Next["wizardHref(next, municipio, entryAction)"]
|
||||
Next -->|Pular/Salvar| Queue
|
||||
Queue -->|vazia| Home["Início / success"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`src/lib/wizardActionChain.ts`** (puro): `wizardChainAfter(entry): CampaignWizardActionId[]`; `nextWizardChainStep(...)`; unit-pinned.
|
||||
- **Pontos de sucesso:** `WizardExpectedVotesStep` (fase saved → replace por redirect ao próximo); `WizardTrendNoteStep` / signal body / leadership continue — após save, consultar fila restantes.
|
||||
- **URLs:** reusar `wizardTrendHref` / `wizardSignalHref` / `wizardActionHref` com `entryAction` = dono da sessão de cadeia (o botão original).
|
||||
- **B97** deve estar verde antes de encadear a partir de tendência (senão o save da tendência loopa e nunca avança).
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Duras: **B96** (Pular nas encadeadas), **B97** (Salvar tendência não loopa). Soft: B61/B63/B64/B70 ✓, placeholder de votos.
|
||||
- Soft: OPS11 só para ver em prod.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Resumo multi-campo + CTA único “Registrar atualização” (UX-1 passo 8) — adiado.
|
||||
- E14 / troca de assessor na cadeia.
|
||||
- Demanda (A5) na matriz v1.
|
||||
- Persistência de rascunho mid-Zap (B59 adiado).
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **XState / orquestrador multi-route genérico.** **Mitigação:** array puro + hrefs.
|
||||
- **Transação única multi-collection.** **Mitigação:** grava por elo.
|
||||
- **Reordenar a strip do Início “porque a cadeia mudou o ritual”.** **Mitigação:** fora (B99 à parte).
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Tela de resumo antes do fim.** Revisitar se CG reportar “não sei o que gravei”.
|
||||
- **Cadeia a partir de sugestão E11.** Quando E11 deep-linkar wizards com município pré-escolhido.
|
||||
|
||||
## Referências
|
||||
|
||||
- [fluxos-acao-primeiro-inicio.md](fluxos-acao-primeiro-inicio.md)
|
||||
- `src/components/campaign/shared/WizardExpectedVotesStep.tsx` (fase `saved` + placeholder)
|
||||
- `src/lib/campaignActionRoutes.ts` · `campaignWizardCopy.ts`
|
||||
- [wizard-header-x-vs-pular.md](wizard-header-x-vs-pular.md) (B96) · [bug-salvar-tendencia-fecha-wizard.md](bug-salvar-tendencia-fecha-wizard.md) (B97)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 4/5 (matriz v1 assumida — validar no gate)
|
||||
118
docs/plans/polimento-busca-municipio-wizard.md
Normal file
118
docs/plans/polimento-busca-municipio-wizard.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Polimento — busca de município no wizard (bullets, gaps, título mobile)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe em `WizardMunicipalitySearchStep` + padding do `CampaignWizardShell`
|
||||
Appetite: ~0,25–0,5d eng; CSS/markup; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` (Clarity under pressure) / `DESIGN.md` · tema `data-theme='campaign'` · B60/B75 shells.
|
||||
|
||||
Na implementação (`work-issue`): craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona / contexto:** CG/assessor no celular no passo 1 do ritual ação→município.
|
||||
- **Job principal:** escanear hits sob a busca sem ruído visual; título “Em qual município?” colado ao chrome vermelho.
|
||||
- **Estratégia de cor:** inalterada.
|
||||
- **Edit where you see:** não — só layout do passo.
|
||||
- **Anti-goals:** redesign da row (`HomeSearchHitRow`); mudar ranking B92–B94; cards.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ header wizard (Mandate Red) ───────────────────────┐
|
||||
│ Ajustar votos [X] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
┌─ conteúdo (pt menor no mobile) ─────────────────────┐
|
||||
│ Em qual município? │ ← perto do header
|
||||
│ ┌─ Buscar município ─────────────────────────────┐ │
|
||||
│ │ Nome do município… │ │
|
||||
│ └────────────────────────────────────────────────┘ │
|
||||
│ ← gap curto (não gap-4) │
|
||||
│ Cairu · Baixo Sul │ ← sem • bullet
|
||||
│ Salinas da Margarida · … │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
Fora do frame: chrome B75; suggest idle B92–B94.
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — apresentação de hits já existentes; sem métrica nova.
|
||||
|
||||
## Contexto
|
||||
|
||||
`WizardMunicipalitySearchStep` renderiza resultados em `<ul className="flex flex-col">` **sem** `list-none` — o UA desenha bullets. O stack interno usa `gap-4` entre input e região de resultados. O shell (`CampaignWizardShell`) aplica `py-6` no `<main>`, afastando o `h1` do header mobile Mandate Red (B75).
|
||||
|
||||
Pedido de produto (2026-08-01): remover bullets; encurtar distância lista↔busca; no mobile, aproximar o título do header.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- Lista de hits sem marcadores (`list-none` + reset `m-0 p-0` no `ul`/`li`, alinhado a `CampaignHomeActionStrip` / grupos do Início).
|
||||
- Gap input→resultados menor (recomendação: `gap-4` → `gap-2` no stack do passo 1; critique pode `gap-1.5`).
|
||||
- No mobile, reduzir padding-top do conteúdo do wizard (recomendação: `main` `pt-3 pb-6 md:py-6` ou equivalente) — título mais perto do header; desktop inalterado ou quase.
|
||||
- Sem migration / action / mudança de copy de empty tipado.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Um item (B95) para os três ajustes da mesma superfície.** Mesmo passo, mesmo PR. **Rejeitado:** três Issues cosméticas (ruído no grafo).
|
||||
- **Só markup/CSS do passo 1 + padding do shell; não reescrever `HomeSearchHitRow`.** **Rejeitado:** inventar row só do wizard.
|
||||
- **Padding do shell: mobile-first no `CampaignWizardShell` (afeta todos os passos).** Título longe do header é problema mobile geral; reduzir `pt` no mobile beneficia tendência/sinal também. **Rejeitado:** wrapper só no search step (inconsistência entre passos). Se critique achar votos/tendência “apertados”, subir `pt` só onde `contentAlign`/`isEntryStep` — gatilho no polish.
|
||||
- **i18n:** classes Tailwind; ids intactos.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **`gap-2` vs `gap-1` entre busca e lista?** **Opções:** A `gap-2` | B `gap-1`. **Recomendação:** **A** — ainda separa input da região live. _(assumido)_
|
||||
- **`pt-3` vs `pt-2` no mobile do shell?** **Opções:** A `pt-3` | B `pt-2`. **Recomendação:** **A** no craft; B se ainda parecer “flutuando”. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Shell["CampaignWizardShell py mobile ↓"] --> H1["h1 stepTitle"]
|
||||
Step["WizardMunicipalitySearchStep"] --> Input["CampaignSearchInput"]
|
||||
Step --> Ul["ul list-none gap curto"]
|
||||
Ul --> Row["HomeSearchHitRow"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`WizardMunicipalitySearchStep.tsx`**: `gap-4` → `gap-2` no flex do passo; `<ul className="m-0 flex list-none flex-col p-0">` + `li` sem padding de lista.
|
||||
- **`CampaignWizardShell.tsx`**: `py-6` → `pt-3 pb-6 md:py-6` (ou token equivalente pós-critique).
|
||||
- **Testes:** unit smoke se já pinam classes; senão assert visual via class string / e2e não obrigatório.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Soft: B60 ✓ / B75 ✓ / B92–B94 (suggest). Nenhuma dura.
|
||||
- Nota: ver empty/suggest em **prod** depende de **OPS11** (deploy); o polimento em si não.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Ranking/geo/continuity do idle → B92–B94.
|
||||
- X vs Pular → **B96**. Encadeamento → **B98**. Strip do Início → **B99**.
|
||||
- Mudar placeholder/empty copy.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **“Já que mexo no shell, redesenho o h1 tipográfico.”** **Mitigação:** só padding.
|
||||
- **Extrair `WizardResultList` genérico.** **Mitigação:** <3 call sites; inline.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Padding diferenciado por `isEntryStep`.** Revisitar se passos longos (nota de tendência) ficarem visualmente colados ao header após o `pt` global mobile.
|
||||
|
||||
## Referências
|
||||
|
||||
- `src/components/campaign/shared/WizardMunicipalitySearchStep.tsx`
|
||||
- `src/components/campaign/shared/CampaignWizardShell.tsx`
|
||||
- `src/components/campaign/dashboard/HomeSearchHitRow.tsx`
|
||||
- [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
|
||||
105
docs/plans/strip-acoes-edge-gap-inicio.md
Normal file
105
docs/plans/strip-acoes-edge-gap-inicio.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Strip de ações do Início — edge-to-edge no mobile + gap menor
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — layout `CampaignHomeActionStrip` / dock do Início
|
||||
Appetite: ~0,25–0,5d eng; CSS; sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` / `DESIGN.md` · B44/B46/B67/B72/B74 strip · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish (iPhone estreito + pan).
|
||||
|
||||
Brief:
|
||||
|
||||
- **Persona:** assessor/CG no Início mobile; polegar na thumb zone.
|
||||
- **Job principal:** ações **nascem na borda** da tela (sem “trilho” de padding); mais botões visíveis com gap menor.
|
||||
- **Anti-goals:** encolher hit target <44 px; quebrar pan B67; mexer no desktop sem necessidade.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
┌─ viewport mobile ──────────────────────────────┐
|
||||
│[ações… →→→ ] │ ← strip cola nas bordas L/R
|
||||
│ ▲ sem padding lateral do content scroll │
|
||||
│ gap entre botões apertado (gap-2) │
|
||||
│ │
|
||||
│ ┌─ busca (continua com padding da página) ─┐ │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — chrome de launcher.
|
||||
|
||||
## Contexto
|
||||
|
||||
O conteúdo de `/campanha` rola dentro de `CampaignContentScroll` com **`p-4 md:p-6`**. A strip (`CampaignHomeActionStrip`) herda esse padding — no mobile sobra faixa vazia entre a borda do aparelho e a primeira/última ação. O gap horizontal já foi `gap-6` → `gap-4` (B72) → `gap-3` (B74); produto (2026-08-01) ainda acha **grande** e pede **sem padding lateral** na strip.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- No mobile, a strip de ações é **edge-to-edge** horizontal (conteúdo da lista pode ter `padding-inline` só o necessário para o **primeiro** snap não colar sob a safe-area / ou bleed com `-mx-4` + `px-0` no scroller, com peek das ações na borda).
|
||||
- Reduzir gap entre botões: `gap-3` → **`gap-2`** (8 px); critique para em `gap-1.5` se ainda folgado.
|
||||
- Manter: pan touch + drag fine (B67), scrollbar oculta, largura do botão / círculo (B58), ordem thumb-zone (B46).
|
||||
- `md+`: pode manter padding da página (edge-to-edge é pedido mobile).
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Bleed só da strip / dock de ações, não remover `p-4` de toda a página.** Busca e resumo continuam com gutter. **Rejeitado:** `p-0` global no Início (esmaga busca/resumo).
|
||||
- **Técnica: negative margin no slot `home-actions` (`-mx-4` compensando o padding do scroll) + scroller full-bleed; opcional `pl-4` só se o primeiro ícone precisar de safe inset — preferir ações na borda como pedido.** **Rejeitado:** duplicar layout fora do `CampaignContentScroll`.
|
||||
- **Item novo (B99), não reabrir B74.** Continuação do aperto. **Rejeitado:** editar só o as-built antigo.
|
||||
- **i18n:** classes; ids `home-actions` intactos.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Primeira ação sob a curved edge / safe-area?** **Opções:** A colar na borda (`px-0`) | B `padding-inline: max(0px, env(safe-area-inset-left))` mínimo. **Recomendação:** **A** no craft (pedido explícito “sem padding”); B se device com notch lateral reclamar. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Scroll["CampaignContentScroll p-4"] --> Dock["home-actions -mx-4"]
|
||||
Dock --> Strip["CampaignHomeActionStrip gap-2"]
|
||||
Strip --> Btns["ActionButton × N"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`CampaignHomeLayout.tsx`** (slot `home-actions`) e/ou **`CampaignHomeActions`**: classes de bleed mobile (`-mx-4 w-[calc(100%+2rem)]` ou equivalente testado).
|
||||
- **`CampaignHomeActionStrip.tsx`**: `gap-3` → `gap-2`.
|
||||
- **Unit:** pin de classes se o spec de layout já existe (`campaignHomeLayout`).
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Soft: B74 ✓ / B67 ✓ / B46 ✓. Nenhuma dura.
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Catálogo de ações / labels.
|
||||
- Wizard chrome / busca do wizard.
|
||||
- Remover padding vertical da thumb zone.
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **Encolher `w-[4.75rem]` “para caber 6 sem pan”.** **Mitigação:** só gap + bleed; novo pedido se ainda faltar.
|
||||
- **Sticky overlay full-bleed fora do scroll.** **Mitigação:** fica no fluxo B46.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Edge-to-edge também no `md` com sidebar.** Revisitar se tablet reclamar de gutter.
|
||||
|
||||
## Referências
|
||||
|
||||
- `src/components/campaign/dashboard/CampaignHomeActionStrip.tsx`
|
||||
- `src/components/campaign/dashboard/CampaignHomeLayout.tsx`
|
||||
- `src/components/campaign/shell/CampaignQuickActionsHost.tsx` (`CampaignContentScroll` padding)
|
||||
- [espacamento-strip-acoes-inicio-v2.md](espacamento-strip-acoes-inicio-v2.md) (B74) · [posicao-botoes-acao-inicio-thumb-zone.md](posicao-botoes-acao-inicio-thumb-zone.md) (B46)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
122
docs/plans/wizard-header-x-vs-pular.md
Normal file
122
docs/plans/wizard-header-x-vs-pular.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# Header do wizard — X na config principal, “Pular” só nas encadeadas
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-01
|
||||
Issue: —
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: B — chrome direito do `CampaignMobileTopBar` / prop `skip` do shell
|
||||
Appetite: ~0,5–1d eng; alinhar resolvers de skip (tendência + parity sinal/liderança); sem migration
|
||||
Responsável: —
|
||||
|
||||
## Design (Impeccable)
|
||||
|
||||
Âncoras: `PRODUCT.md` / `DESIGN.md` · B75 header Mandate Red · tema `campaign`.
|
||||
|
||||
Na implementação: craft compacto → critique → polish.
|
||||
|
||||
Brief compacto:
|
||||
|
||||
- **Persona:** CG/assessor no meio do ritual; acabou de escolher o município.
|
||||
- **Job principal:** sair da ação **sem** executar a config principal = **X**; pular um ajuste **opcional** depois da principal = **“Pular …”**.
|
||||
- **Estratégia de cor:** inalterada (ghost no header vermelho).
|
||||
- **Edit where you see:** não — chrome.
|
||||
- **Anti-goals:** X e Pular juntos; “Pular” quando ainda não houve commit da ação disparada; mudar copy do fluxo no centro do header.
|
||||
|
||||
### Wireframe (texto)
|
||||
|
||||
```text
|
||||
Ação disparada = Mudar tendência (standalone)
|
||||
┌─ header ────────────────────────────────────────────┐
|
||||
│ [Voltar?] Mudar tendência · Cairu [X]│ ← X (ainda sem salvar tendência)
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
Depois de Ajustar votos (principal OK) → encadeada tendência
|
||||
┌─ header ────────────────────────────────────────────┐
|
||||
│ [Voltar] Mudar tendência · Cairu [Pular … →] │ ← Pular (votos já gravados)
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Dados → decisão → apresentação
|
||||
|
||||
Dados: N/A — chrome de fluxo.
|
||||
|
||||
## Contexto
|
||||
|
||||
**B75 ✓** definiu: slot direito = **X** (`dismissHref`) **exceto** quando `skip` está setado → aí vira “Pular <fluxo>”.
|
||||
|
||||
Hoje:
|
||||
|
||||
| Fluxo | Skip quando? | Comportamento |
|
||||
| ------------- | --------------------------------------- | ------------------------------------------------------------- |
|
||||
| Sinal | `entryAction` ≠ `register-signal` | Correto (standalone = X) |
|
||||
| Liderança | `showLeadershipWizardSkip(entryAction)` | Alinhado ao sinal |
|
||||
| **Tendência** | `resolveWizardTrendSkip()` **sempre** | **Errado** — standalone mostra “Pular mudança de tendência →” |
|
||||
|
||||
Pedido de produto (2026-08-01): na **primeira configuração** da ação disparada (após município), o direito é **X** (sair sem executar a config principal). Configurações **encadeadas** (após a principal concluída) recebem **“Pular”**. Vale para **todos** os fluxos até a principal fechar.
|
||||
|
||||
## Objetivos
|
||||
|
||||
- `resolveWizardTrendSkip(entryAction)` espelha o sinal: **só** retorna skip quando a tendência é encadeada (`entryAction` presente e ≠ `change-trend`); standalone → `undefined` → header mostra X.
|
||||
- Auditar votos / demanda / stubs: main config **sem** `skip` até B98 passar `entryAction` nas encadeadas.
|
||||
- Unit: tendência standalone = sem skip; com `entryAction: 'update-votes'` = skip label + href.
|
||||
- Guardrails: sem migration; dismiss continua Início; não inventar segundo botão.
|
||||
|
||||
## Decisões travadas
|
||||
|
||||
- **Regra canônica: skip iff o passo atual é subfluxo encadeado (origem ≠ ação dona do passo).** Fonte: produto 2026-08-01 + B75 + precedente B63 sinal. **Rejeitado:** skip sempre na tendência (B64 as-written — supersedido); X+Pular juntos.
|
||||
- **Corrigir tendência agora; wiring de cadeia completa = B98.** Este item só alinha o chrome quando `entryAction` já existe (sinal/liderança/tendência embutidos). **Rejeitado:** implementar toda a matriz de encadeamento aqui (explode appetite).
|
||||
- **Label de skip permanece específica do subfluxo** (“Pular mudança de tendência →”, etc.). **Rejeitado:** “Pular” genérico sem nome (B75 pede nome do fluxo).
|
||||
- **i18n:** `entryAction`, `resolveWizardTrendSkip`; copy existente.
|
||||
|
||||
## Questões em aberto
|
||||
|
||||
- **Href do skip encadeado: Início vs próximo da cadeia?** **Opções:** A Início (hoje) | B próximo passo / fim da cadeia (B98). **Recomendação:** **A neste item**; B98 redefine o destino do Pular quando houver fila. _(assumido)_
|
||||
|
||||
## Abordagem proposta
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Step["Wizard*Step"] --> Resolve["resolve*Skip(entryAction)"]
|
||||
Resolve -->|undefined| Shell["skip omitido"]
|
||||
Shell --> Bar["TopBar → X dismissHref"]
|
||||
Resolve -->|skip| Shell2["skip label+href"]
|
||||
Shell2 --> Bar2["TopBar → Pular"]
|
||||
```
|
||||
|
||||
Componentes:
|
||||
|
||||
- **`src/lib/politicalTrendWizardUi.ts`**: `resolveWizardTrendSkip(entryAction?)` no padrão de `resolveWizardSignalSkip`; `shouldShowWizardTrendSkip`.
|
||||
- **`WizardTrendChoiceStep` / `WizardTrendNoteStep`**: passar `entryAction` ao resolver; `trailingAction` só se skip.
|
||||
- **Parity check** em `wizardSignalUi` / leadership skip helpers (já ok) — pin unit compartilhado se extrair predicado `isChainedWizardStep(entry, owner)` com 3º call site; senão duplicar o predicado de uma linha.
|
||||
- **Migration:** Sem migration.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Soft: B75 ✓, B63/B64/B70. Dura para ver Pular “de verdade” após votos: **B98**.
|
||||
- Não bloqueia **B97** (fechar no Salvar).
|
||||
|
||||
## Não escopo
|
||||
|
||||
- Orquestrar cadeia de passos → **B98**.
|
||||
- Fechar wizard no Salvar da tendência → **B97**.
|
||||
- Skip no desktop no header vermelho (já adiado em B75).
|
||||
|
||||
## Rabbit holes
|
||||
|
||||
- **State machine global de “fase principal vs encadeada”.** **Mitigação:** `entryAction` query já é o sinal; B98 só popula.
|
||||
- **Renomear todos os labels de Pular.** **Mitigação:** fora.
|
||||
|
||||
## Adiado com gatilho
|
||||
|
||||
- **Destino do Pular = próximo da fila.** Revisitar em **B98**.
|
||||
|
||||
## Referências
|
||||
|
||||
- `src/lib/politicalTrendWizardUi.ts` · `src/lib/wizardSignalUi.ts`
|
||||
- `src/components/campaign/municipality/WizardTrendChoiceStep.tsx` / `WizardTrendNoteStep.tsx`
|
||||
- `src/components/campaign/shell/CampaignMobileTopBar.tsx`
|
||||
- [header-mobile-wizard-campanha.md](header-mobile-wizard-campanha.md) (B75) · [wizard-mudar-tendencia.md](wizard-mudar-tendencia.md) (B64 — skip sempre, supersedido aqui)
|
||||
- `PRODUCT.md` / `DESIGN.md`
|
||||
|
||||
Qualidade de decisão: 5/5
|
||||
Loading…
Reference in a new issue