plans(OPS28-30): flaky e2e — paridade de seed no worktree, estado vazio do suggest, hardening dev compile-frio (Related #584 #585 #586)
This commit is contained in:
parent
972c6ac8ba
commit
b95682da9f
3 changed files with 209 additions and 0 deletions
67
docs/plans/busca-sem-sugestoes-estado-vazio-honesto.md
Normal file
67
docs/plans/busca-sem-sugestoes-estado-vazio-honesto.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Busca global sem sugestões mostra região em branco — estado vazio honesto
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-10
|
||||
Issue: #585
|
||||
Priority: P2
|
||||
Model: composer-2.5
|
||||
Impeccable: B — encaixe na superfície existente (região de resultados do search); só copy/label, sem rearranjo
|
||||
Canvas UI: N/A — só copy/label em superfície existente (ver ui-draft-canvas.md, linha "Só copy/label sem rearranjo")
|
||||
Appetite: ~0,5 dia eng; um outcome verificável
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
No search global (`/campanha` Início e o FAB overlay "Ações rápidas"), coordenador/candidato que foca o campo sem digitar recebe as "Sugestões" — municípios com `priority: alta` curados pela equipe (B68). Quando **nenhum** município está priorizado (ambiente recém-provisionado, sem importação da planilha de projeção, ou equipe que ainda não curou nada), o campo foca e a região de resultados renderiza **vazia, sem texto, sem affordance**: o usuário não sabe se o search quebrou, carregou ou não tem nada a mostrar. Medido via trace: POST `/campanha/home-search` responde `success/resultKind:suggest` com `municipalities: []` e a região fica em branco.
|
||||
|
||||
Não é um caso raro: é o estado natural de qualquer banco antes do staff importar a projeção. O teste e2e do overlay (que espera a região `Sugestões`) hoje só passa onde há dados — e deveria poder passar em qualquer ambiente **se o estado vazio for honesto e verificado**.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenador ou assessor em ambiente novo (ou antes da 1ª importação de projeção) abrindo o search do Início ou o FAB.
|
||||
- **Job principal:** saber, em meio segundo, que o search está vivo e que não há sugestões ainda — sem assumir que quebrou.
|
||||
- **Fluxo desejado:** foca o campo → vê uma mensagem curta ("Nenhuma sugestão ainda — priorize municípios na planilha de projeção" ou equivalente, no tom do app) → digita → resultados de busca normais aparecem.
|
||||
- **Anti-goals de produto:** não inventar sugestões falsas (sem dados não há curadoria); não transformar o empty em um componente pesado de onboarding; não mudar o comportamento quando há sugestões.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Com zero municípios priorizados, focar o search (Início e FAB overlay) mostra uma mensagem de estado vazio clara dentro da região de resultados — nunca uma região muda.
|
||||
- Com ≥1 município priorizado, o comportamento atual é preservado byte a byte (região `Sugestões` com os hits).
|
||||
- O e2e do FAB overlay passa em **qualquer** estado de dados (vazio ou com sugestões) — o teste ganha uma asserção para o estado vazio ou uma expectativa tolerante a ambos, decidido no plano de implementação sem enfraquecer o contrato.
|
||||
- Nenhuma mudança na API `/campanha/home-search` (o cliente já tem tudo que precisa: `status: success` + listas vazias).
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — é estado vazio de UI; sem métrica nova.
|
||||
- **Decisões desbloqueadas:** usuário decide "o search está funcionando, só não há curadoria ainda" em vez de "quebrou".
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/components/campaign/dashboard/HomeSearchResultsShell.tsx` (onde o vazio de hits já é tratado para o modo busca — `Nenhum resultado.` — mas não para o modo sugestão), `src/components/campaign/dashboard/CampaignHomeSearch.tsx` (região `Resultados da busca`), `src/components/campaign/shell/CampaignQuickActionsOverlay.tsx` (FAB).
|
||||
- **Precedente a olhar:** o empty-state de `query.isActive` (`Nenhum resultado.` no shell) e o `emptyState` do `SuggestionsPanel` (E11) — o app já tem a gramática de vazios.
|
||||
- **Risco de acoplamento:** o modo sugestão alimenta três superfícies (Início, FAB overlay desktop, Drawer mobile) pelo mesmo estado — a mensagem deve vir do estado compartilhado, não de uma cópia por superfície; leader lockdown não é tocado (leader nem tem search).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma (o item OPS28 — seed no worktree — reduz o número de ambientes vazios, mas o estado vazio existe por design e vale ser honesto em qualquer banco).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Curar sugestões por outro critério (frescura de sinal, déficit de meta) quando não há `alta` — mudança de política de produto, precisa de evidência; destino: discutir com produto, não este item.
|
||||
- Qualquer mudança no ranking `rankHomeSearchSuggestMunicipalities` ou na API.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Sem dados, não mostra nada — é o estado correto."** Tecnicamente verdade, mas uma região que não diz nada é indistinguível de quebrada; o custo do empty é ~uma linha e ele vira o contrato testável em qualquer ambiente. **Corte neste item:** mensagem curta e única, sem ilustrações, sem CTA para tela nova.
|
||||
- **"Aproveitar e adicionar sugestões por outro critério."** Mudar a curadoria é decisão de produto com evidência de uso; vazia hoje, a área é a prova de que não há critério alternativo aceito. **Corte:** este item só torna o vazio legível.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Qual o texto do estado vazio?** **Opções:** A) "Nenhuma sugestão ainda — priorize municípios na planilha de projeção" | B) "Sem sugestões por enquanto." | C) sem texto, um hint discreto. **Recomendação:** A — diz o que é, por quê e o que desbloqueia, no tom direto do app. _(assumido — validar com produto)_
|
||||
- **O empty vale também para o assessor (carteira sem sinal)?** **Opções:** A) sim, mesma mensagem | B) mensagem específica por papel | C) assessor não tem empty (carteira sempre lista algo). **Recomendação:** C — a carteira do assessor sempre tem municípios; o empty só se aplica quando a lista do papel pode nascer vazia (unrestricted sem `alta`). Validar no plano de implementação com a regra real do ranking.
|
||||
|
||||
## Referências
|
||||
|
||||
- Trace da sessão: POST `/campanha/home-search` (`mode: suggest`) → `200`, `status: success`, `resultKind: suggest`, `municipalities: []`, `scopeMunicipalities: 435`; snapshot do FAB com `region "Resultados da busca"` vazia.
|
||||
- `src/components/campaign/dashboard/HomeSearchResultsShell.tsx`, `HomeSearchMunicipalityGroup.tsx` (a seção `Sugestões` retorna `null` sem hits — é aí que o vazio fica invisível).
|
||||
- `tests/e2e/campaignMunicipalities.e2e.spec.ts:1063` — spec que hoje depende de dados para a asserção `region 'Sugestões'`.
|
||||
71
docs/plans/hardening-e2e-dev-compile-frio.md
Normal file
71
docs/plans/hardening-e2e-dev-compile-frio.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Hardening e2e em dev: prewarm de rotas frias + login resiliente a compile
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-10
|
||||
Issue: #586
|
||||
Priority: P2
|
||||
Model: composer-2.5
|
||||
Impeccable: A — N/A (sem UI; infra de testes)
|
||||
Canvas UI: N/A — sem UI
|
||||
Appetite: ~0,5–1 dia eng; um outcome verificável
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
Dois flakes e2e reproduzidos nesta sessão em dev (worktree, 2 workers) na suíte que a CI roda verde — a classe "compile-frio do dev server" que faz `pnpm test:e2e` local acusar vermelho em `main` verde:
|
||||
|
||||
1. **`campaignMunicipalities` "coordinator opens the municipalities list, edits strategy and assigns an advisor" — 3/4 runs falharam** com `page.goto: net::ERR_ABORTED; maybe frame was detached?` navegando para `/campanha/municipios/<slug>/editar` (linha 107). O mecanismo está documentado no próprio `setup.e2e.spec.ts` (linhas 26-30): "Next dev compiles a route on its first hit, and that compile can trigger a full-page reload for any client currently connected — which aborts an in-flight fetch mid test". O setup prewarms o detail (`/campanha/municipios/e2e-prewarm`) **mas não a rota `/editar`** — que é a única rota de edição que specs navegam (verificado por grep) e compila fria no meio do teste.
|
||||
2. **`campaignMunicipalities` "coordinator sets trend status and justification with auto-save (B24)" — 1/2 runs falhou** com `page.waitForURL` (login do fixture) estourando os 60 s: o login é a primeira jornada pesada do teste e o compile frio do server action de login + as rotas do `(app)` sob 2 workers não couberam no budget. O comentário do `playwright.config.ts` já registra a classe (P3-C mediu login estourando o budget antigo); o budget de 60 s cobre a maioria, mas não todas.
|
||||
|
||||
Ambos são **infra de teste/dev-mode**, não defeito de produto: o fluxo real do usuário (clicar em Editar; fazer login) funciona — é a navegação do browser racionando contra o rebuild do dev server. A CI usa o build de produção (`pnpm start`) e não tem esse mecanismo.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** agente ou humano rodando `pnpm test:e2e` em dev (padrão local, inclusive worktrees).
|
||||
- **Job principal:** rodar a suíte local e ler o resultado como espelho da CI.
|
||||
- **Fluxo desejado:** `pnpm test:e2e` em dev → verde estável para o que é verde em prod/CI; vermelho só por regressão real.
|
||||
- **Anti-goals de produto:** não tocar asserções de produto (nenhuma asserção desses testes está errada); não mudar o comportamento do app para satisfazer o dev server.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- `campaignMunicipalities` (arquivo inteiro) roda **verde ×3 consecutivos** em dev a 2 workers — incluindo `:78` e `:518` — nesta sessão de verificação.
|
||||
- O setup do e2e prewarms as rotas que specs navegam de fato (incl. `/campanha/municipios/e2e-prewarm/editar` e qualquer outra rota de edição usada por specs — verificar por grep, não por palpite), seguindo o mecanismo já documentado no setup.
|
||||
- O login do fixture fica resiliente a compile frio (ex.: retry-único da espera de navegação, ou budget maior só no login) **sem** enfraquecer asserção de produto — login é setup, não contrato.
|
||||
- P4-L verificado e fechado no ledger: `campaignLeaderships` (chips) era "falha determinística em dev na árvore limpa" (row 59 do TECH-DEBT); o spec foi reescrito pelo B34 desde então e passou 2/2 nesta sessão — re-rodar ×3 em dev + 1× com `E2E_PROD=1` e atualizar o ledger com a evidência.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — confiabilidade do gate local; o "dado" é estabilidade de execução.
|
||||
- **Decisões desbloqueadas:** "e2e local verde = entrega pronta para CI" sem re-tentativas de máquina.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `tests/e2e/setup.e2e.spec.ts` (lista de prewarm — GET e POST), `tests/e2e/fixtures/campaignE2EFixtures.ts` (`campaign.login`, linha ~546-555), `docs/TECH-DEBT.md` (row 59 — fechar P4-L com evidência).
|
||||
- **Precedente a olhar:** o guard `e2eNavigationConventions` (miss #54) que bane goto→goto sem settle; o comentário do próprio setup sobre o mecanismo de full-reload; P4-L documentado em `docs/plans/entrega-engenharia-p4.md` (onda 4, "Debug com evidência").
|
||||
- **Risco de acoplamento:** o prewarm não pode virar "aguardar resposta 200 de rota autenticada" (o setup roda sem sessão — as GETs de prewarm usam `e2e-prewarm` justamente por isso); o retry de login não pode engolir falha real de credencial (o retry só na espera de navegação, não na validação de credenciais).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma (a investigação do B188 — chat Sollinha — é da #562, em progresso; se o executor encontrar evidência de que o contexto do chat interfere nessas navegações, registrar como achado da #562, não reabrir o assunto aqui).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Flake de dados de seed (`campaignMunicipalities` FAB suggest, `campaignSavedFilters`) → OPS28 (provisionamento) / OPS29 (estado vazio).
|
||||
- Flakes já registrados: #562 (agenda mobile, em progresso), #553, #573.
|
||||
- Refatorar o spec `campaignMunicipalities` além do mínimo para a estabilidade.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Adicionar `waitForLoadState('networkidle')` antes de todo goto."** Esconde o flake por sorte e deixa a suíte mais lenta; o prewarm ataca a causa (rota quente antes do teste). **Corte neste item:** prewarm das rotas reais + retry-único só no login.
|
||||
- **"Aproveitar e trocar o dev server por build de produção no padrão local."** Isso é decisão de fluxo de dev (já existe `E2E_PROD=1` como modo honesto documentado); muda o trade-off de velocidade para todo mundo. **Corte:** deixar o padrão `pnpm dev` intacto.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **O retry de login deve ser no fixture (para todos os specs) ou um helper opcional?** **Opções:** A) retry-único dentro do `campaign.login` (todos os specs herdam) | B) helper que os specs chamam quando sabem que estão em dev. **Recomendação:** A — login é infra em todos os specs; um helper opcional volta a depender de cada spec lembrar. _(assumido — validar com quem mantém os fixtures)_
|
||||
- **P4-L: fechar o ledger ou manter aberto com "verificado"?** **Opções:** A) fechar com evidência (×3 dev + 1 prod) | B) manter aberto como dívida de verificação contínua. **Recomendação:** A — dívida fechada com evidência é o padrão do ledger; reabrir se um agente reproduzir com a spec nova.
|
||||
|
||||
## Referências
|
||||
|
||||
- Evidência da sessão: `:78` falhou 3× e passou 1× (sempre `ERR_ABORTED` no goto de `/editar`); `:518` falhou 1× (`waitForURL` do login, 60 s); `campaignLeaderships` 2/2 verde (spec reescrita pós-B34).
|
||||
- `tests/e2e/setup.e2e.spec.ts` (mecanismo do full-reload + lista de prewarm atual), `playwright.config.ts` (comentários de budget P3-C).
|
||||
- `docs/TECH-DEBT.md` row 59 (P4-L) e `docs/plans/entrega-engenharia-p4.md` (onda 4).
|
||||
71
docs/plans/worktree-seed-minimal-paridade-e2e-ci.md
Normal file
71
docs/plans/worktree-seed-minimal-paridade-e2e-ci.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Worktrees provisionam os bancos e2e sem o seed mínimo — e2e diverge da CI
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-10
|
||||
Issue: #584
|
||||
Priority: P1
|
||||
Model: composer-2.5
|
||||
Impeccable: A — N/A (sem UI; infra de provisionamento de testes)
|
||||
Canvas UI: N/A — sem UI
|
||||
Appetite: ~0,5 dia eng; um outcome verificável
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
`pnpm worktree next/plan` cria bancos `teqo_wt<slot>` / `teqo_wt<slot>_test` **só com migrations** (documentado em AGENTS.md e `scripts/worktree.mjs`). A CI roda `pnpm migrate && pnpm db:seed:minimal` antes do e2e. Resultado: o e2e em worktree testa **dados diferentes** dos que a CI testa — e o e2e local de um worktree fica vermelho em `main` verde, ensinando agentes a ignorar a suíte (o padrão exato que o ledger TECH-DEBT row 19 chama de "masks real failures in the delivery gate").
|
||||
|
||||
Reproduzido nesta sessão (worktree `plan-issue-23`, banco fresco só-migrations):
|
||||
|
||||
- `campaignMunicipalities` "FAB overlay polish — overlay labels readable and search focus hides action strip" — 2/2 falhas: o POST `/campanha/home-search` (`mode: suggest`) responde `success/resultKind:suggest` com **0 municípios**, e a região `Sugestões` nunca renderiza. Causa: `rankHomeSearchSuggestMunicipalities` para coordenador/candidato filtra `priority === 'alta'`, e o catálogo migrado nasce 435×`normal` (medido no banco: `normal|435`, zero `alta`). O `db:seed:minimal` é quem pina `salvador-ze-1` e `camacari` como `alta` — por isso a CI passa (48/48×3 em 2026-07-31) e o worktree falha.
|
||||
- `campaignSavedFilters` "saves the current recorte…" — 1/1 falha: o recorte `?priority=alta` (416 municípios esperados, "deep enough that `?page=2` is a real page") nasce **vazio** sem os pins do seed → o nome sugerido do recorte vem vazio e o fluxo quebra na linha 49.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** agente ou humano rodando `pnpm test:e2e` num worktree provisionado — o ambiente padrão de entrega em paralelo.
|
||||
- **Job principal:** rodar o e2e local e confiar no resultado como espelho da CI.
|
||||
- **Fluxo desejado:** `worktree next` (ou `plan`) → bancos prontos com o mesmo conteúdo sintético da CI → `pnpm test:e2e` verde no que é verde na CI → vermelho só no que é vermelho de verdade.
|
||||
- **Anti-goals de produto:** não alterar nenhum spec para "aceitar dados ausentes" enquanto o provisionamento é o defeito; não rodar seed contra banco não-local.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Um worktree recém-provisionado (`next` **e** `plan`) roda `pnpm test:e2e` (suíte afetada) com o mesmo resultado esperado da CI para os specs dependentes de dados do seed (`campaignMunicipalities` FAB suggest, `campaignSavedFilters`, e os demais que tocam `priority`/`expectedVotes`/`campaignGoals`).
|
||||
- A CI continua sendo a fonte canônica: nada do que este item faz muda o que a CI executa.
|
||||
- O seed mínimo roda idempotente (upsert) e protegido pelo guard de banco local; re-provisionamento não duplica nem corrompe.
|
||||
- Guard de regressão: se o manifest do seed mínimo perder os pins que specs e2e dependem, algo falha (teste de convenção ou o próprio e2e) — decidir no plano de implementação, sem enfraquecer specs.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — infra de provisionamento; o "dado" é a paridade de estados entre ambientes.
|
||||
- **Decisões desbloqueadas:** agente decide "e2e local verde/vermelho é confiável" sem re-checar contra a CI.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `scripts/worktree.mjs` (provisionamento — `runMigrate`/criação dos bancos), `scripts/seed-minimal.mjs` + `scripts/lib/seed-minimal-manifest.mjs` (fonte do conteúdo sintético), `tests/e2e/setup.e2e.spec.ts` (onde a paridade de expectativas já vive).
|
||||
- **Precedente a olhar:** AGENTS.md "Per-worktree environments" (2026-08-08) documenta o contrato atual; `tests/unit/seedMinimalManifest.unit.spec.ts` pina o manifest; guard `assertLocalDatabase` é o portão de segurança existente.
|
||||
- **Risco de acoplamento:** o seed é idempotente e local-only; nunca apontar para Neon; specs e2e **não** são tocados neste item (a menos que um spec prove depender de dado que nem a CI garante — aí é outro item).
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Estado vazio do search/suggest no produto (coordenador sem municípios priorizados vê região em branco) → item próprio (OPS29).
|
||||
- Flakes de compile-frio do dev server (`:78`/`:518` da lista de municípios) → item próprio (OPS30).
|
||||
- Flakes já registrados: #562 (agenda mobile CI, em progresso), #553 (campaignSuggestions/testDatabaseLease), #573 (unit `municipalitiesWithoutUpdateTool`).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Corrigir o teste em vez do provisionamento."** Se alguém ajustar `campaignSavedFilters`/FAB para tolerar lista vazia: o worktree volta a mascarar diferenças de dados e o próximo spec que depender de seed quebra sem aviso. **Corte neste item:** a paridade de provisionamento é o contrato; spec muda só com evidência de que nem a CI garante o dado.
|
||||
- **"Rodar o seed em todo banco do sistema."** O escopo é o provisionamento de worktrees; o `teqo`/`teqo_test` compartilhados e a CI já têm seus fluxos.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **O seed mínimo deve rodar também no `teqo`/`teqo_test` compartilhados no fallback sem Docker?** **Opções:** A) sim, por paridade total | B) não, deixa o fluxo manual atual. **Recomendação:** A — o fallback Cursor Cloud é o ambiente mais provável de pegar banco cru; o seed é idempotente. _(assumido — validar com produto/ops)_
|
||||
- **Onde entra o guard de paridade (spec pina que o seed existe)?** **Opções:** A) no manifest unit (estende o pin existente) | B) no setup e2e (falha cedo com mensagem clara) | C) nenhum, a CI pega. **Recomendação:** B — o setup já é o lugar onde o e2e declara suas precondições de dados; mensagem clara em vez de spec vermelho confuso.
|
||||
|
||||
## Referências
|
||||
|
||||
- Evidência da sessão: banco `teqo_wt959_test` com `priority: normal|435` (zero `alta`, zero contatos) vs `scripts/lib/seed-minimal-manifest.mjs` pins `salvador-ze-1`/`camacari` `alta`; trace de rede do POST `/campanha/home-search` respondendo `municipalities: []` com `scopeMunicipalities: 435`.
|
||||
- `AGENTS.md` — "Per-worktree environments (`pnpm worktree next`, 2026-08-08)" (contrato atual: migrations only).
|
||||
- `docs/TECH-DEBT.md` row 19 — flake fichado "masks real failures in the delivery gate".
|
||||
Loading…
Reference in a new issue