From 20b9f336cdef7b81c17e093e346e999389f51c7e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 10:39:18 +0000 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20rascunho=20OPS11=20+=20B95=E2=80=93?= =?UTF-8?q?B99=20(deploy=20cooldown,=20wizard=20polish/chain)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Planos locais pré-gate plan-issue: cooldown 30min no deploy Vercel, polimento busca município, X vs Pular, bug Salvar tendência, encadeamento de ajustes, strip edge-to-edge. Co-authored-by: Francisco Solla --- .../bug-salvar-tendencia-fecha-wizard.md | 120 ++++++++++++++++ docs/plans/ci-deploy-cooldown-vercel.md | 99 +++++++++++++ docs/plans/encadear-ajustes-wizard.md | 133 ++++++++++++++++++ .../plans/polimento-busca-municipio-wizard.md | 118 ++++++++++++++++ docs/plans/strip-acoes-edge-gap-inicio.md | 105 ++++++++++++++ docs/plans/wizard-header-x-vs-pular.md | 122 ++++++++++++++++ 6 files changed, 697 insertions(+) create mode 100644 docs/plans/bug-salvar-tendencia-fecha-wizard.md create mode 100644 docs/plans/ci-deploy-cooldown-vercel.md create mode 100644 docs/plans/encadear-ajustes-wizard.md create mode 100644 docs/plans/polimento-busca-municipio-wizard.md create mode 100644 docs/plans/strip-acoes-edge-gap-inicio.md create mode 100644 docs/plans/wizard-header-x-vs-pular.md diff --git a/docs/plans/bug-salvar-tendencia-fecha-wizard.md b/docs/plans/bug-salvar-tendencia-fecha-wizard.md new file mode 100644 index 00000000..1b3b08b7 --- /dev/null +++ b/docs/plans/bug-salvar-tendencia-fecha-wizard.md @@ -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 diff --git a/docs/plans/ci-deploy-cooldown-vercel.md b/docs/plans/ci-deploy-cooldown-vercel.md new file mode 100644 index 00000000..228d7e8b --- /dev/null +++ b/docs/plans/ci-deploy-cooldown-vercel.md @@ -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 --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 diff --git a/docs/plans/encadear-ajustes-wizard.md b/docs/plans/encadear-ajustes-wizard.md new file mode 100644 index 00000000..55a6470d --- /dev/null +++ b/docs/plans/encadear-ajustes-wizard.md @@ -0,0 +1,133 @@ +# 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) diff --git a/docs/plans/polimento-busca-municipio-wizard.md b/docs/plans/polimento-busca-municipio-wizard.md new file mode 100644 index 00000000..f63b6cb8 --- /dev/null +++ b/docs/plans/polimento-busca-municipio-wizard.md @@ -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 `
    ` **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 `
    `, 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; `
      ` + `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 diff --git a/docs/plans/strip-acoes-edge-gap-inicio.md b/docs/plans/strip-acoes-edge-gap-inicio.md new file mode 100644 index 00000000..5a8934e8 --- /dev/null +++ b/docs/plans/strip-acoes-edge-gap-inicio.md @@ -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 diff --git a/docs/plans/wizard-header-x-vs-pular.md b/docs/plans/wizard-header-x-vs-pular.md new file mode 100644 index 00000000..84e3504b --- /dev/null +++ b/docs/plans/wizard-header-x-vs-pular.md @@ -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 From 84c8b56a550f6c5d7c8955b5e39b1e3bd73d55a1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 10:40:10 +0000 Subject: [PATCH 2/2] style: prettier nos planos B96/B98 --- docs/plans/encadear-ajustes-wizard.md | 13 +++++++------ docs/plans/wizard-header-x-vs-pular.md | 10 +++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/plans/encadear-ajustes-wizard.md b/docs/plans/encadear-ajustes-wizard.md index 55a6470d..4819fe5f 100644 --- a/docs/plans/encadear-ajustes-wizard.md +++ b/docs/plans/encadear-ajustes-wizard.md @@ -63,14 +63,15 @@ Pedido (2026-08-01): ao iniciar uma ação → município → **ajuste principal - **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) | + | 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. diff --git a/docs/plans/wizard-header-x-vs-pular.md b/docs/plans/wizard-header-x-vs-pular.md index 84e3504b..8e575c22 100644 --- a/docs/plans/wizard-header-x-vs-pular.md +++ b/docs/plans/wizard-header-x-vs-pular.md @@ -47,11 +47,11 @@ Dados: N/A — chrome de 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 →” | +| 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.