9.2 KiB
Impl: Status de apoio "Lembrança" em lideranças
Status: em execução Atualizado em: 2026-08-11 Issue: #661 Intenção: docs/plans/lembranca-status-liderancas.md Appetite restante: herdado (~1 dia eng) — item pequeno, cabe folgado
Leitura da intenção
- Outcome: novo valor
lembrancano enumsupportStatusdeleadership(entreem_disputaenegativono seletor), visível e marcável em TODAS as superfícies onde os 4 status atuais aparecem (célula da lista com auto-save, ficha/wizard/admin, filtro das listas de lideranças e de pessoas, Sollinha); nunca entra no critério de pendência de abordagem (fail-closed na query, não só na UI); badge visualmente distinto; nenhuma leitura existente quebra. - O que NÃO negociar: liderança "Lembrança" é favorável e não é pendente — exclusão das pendências espelhando o "Negativo" (na query
getPendingLeaderships, que já é fail-closed); sem segundo eixo de classificação; sem fluxo próprio de votos (usa o campo de votos declarados existente); leader lockdown intacto (status é staff-only). - O que reavaliar: a hipótese da intenção cobre as áreas certas, mas encontrei duas superfícies que ela não nomeou: (1)
peopleData.tsleadershipStatusesInOrder— lista dura dos 4 valores que silenciosamente omitiria o novo status do facet da lista de pessoas; (2)getLeaderships.tsmantém uma tabela de rótulos duplicada (SUPPORT_LABELS) que também esqueceria o valor novo — candidata a DRY comsupportStatusLabels.
Abordagem recomendada
flowchart LR
A[leadershipSupportStatuses\n+ 'lembranca'] --> B[collection options\n+ migration ALTER TYPE]
A --> C[labels + badge variant\n+ tokens CSS]
A --> D[listas/filtros/forms\nvia enum array]
A --> E[getLeaderships usa\nsupportStatusLabels]
F[getPendingLeaderships\nINTOCADO + teste pin] --> G[pendências excluem\nlembranca fail-closed]
H[systemPrompt +1 linha\nreconhecimento] --> I[Sollinha responde\nsobre o status]
Opções consideradas: A (valor novo no enum existente, fonte única) | B (segundo campo/eixo de classificação) | C (status composto em string)
Recomendação: A — o produto decidiu no gate que é um valor a mais no enum; a engenharia tem um único source of truth (leadershipSupportStatuses + supportStatusLabels) e as superfícies que derivam dele (listas, forms, URLs, omnibox) ganham o valor automaticamente.
Rejeitadas: B porque a intenção corta explicitamente o segundo eixo (matriz de combinações); C porque destrói filtro in no Postgres e indexação.
Decisões de engenharia
- D1 — Migration do enum.
ALTER TYPE "public"."enum_leadership_support_status" ADD VALUE 'lembranca' BEFORE 'negativo'— oBEFOREpreserva a ordem do enum no Postgres igual à do seletor (decisão do gate: entre "Em disputa" e "Negativo"), entãosort: 'supportStatus'na lista ordena igual ao seletor.- Rejeitadas:
ADD VALUEsemBEFORE(anexa no fim → ordenação por status divergiria do seletor); recriar o tipo inteiro (rename+create+cast, precedente C14) — destrutivo demais para um valor novo, risco desnecessário em prod. - Precedentes:
20260723_025513(ADD VALUE … BEFORE),20260728_041547/20260724_180000(ADD VALUE). Se omigrate:creategerarADD VALUEsemBEFORE, editar o.tsda migration nova (migration não shipada — editável; nunca editar as antigas).
- Rejeitadas:
- D2 — Badge distinto. Novo variant
support-rememberednoBadge+ tokens--support-remembered/-foreground(índigo suave:#e0e7ff/#3730a3) — distinto dos 4 atuais (verde/cinza/âmbar/vermelho) e semanticamente "resolvido e favorável".- Rejeitadas: reusar
estimate-*(âmbar colide com "Em disputa"); variantdefault(não distinto).
- Rejeitadas: reusar
- D3 —
getLeaderships.ts: matar a tabela duplicada. SubstituirSUPPORT_LABELSporsupportStatusLabelsdeleadershipLabels.ts(tabela de strings pura, client-safe, importável do server). Um valor novo no enum seria propagado pela fonte única em vez de exigir lembrar da duplicata.- Rejeitada: só adicionar
lembrancana duplicata — deixa a segunda fonte de verdade viva para o próximo status.
- Rejeitada: só adicionar
- D4 —
peopleData.tsleadershipStatusesInOrder: trocar a lista dura['engajado','a_abordar','em_disputa','negativo']porleadershipSupportStatuses.filter(s => statuses.has(s)). Sem isso o status novo não apareceria no facet da lista de pessoas (miss silencioso, sem teste pegando). - D5 — Pendência: não tocar.
PENDING_SUPPORT_STATUSES = ['a_abordar','em_disputa','engajado']ePENDING_CRITERIONficam como estão — "Lembrança" e "Negativo" já são excluídos pela ausência da lista (fail-closed na query) e o critério declarado continua sem o novo estado (aceite: "pendências declaram o critério sem o novo estado"). Adicionar teste que pin a exclusão. - D6 — Sollinha reconhece o status: +1 linha na seção "Lideranças pendentes de abordagem" do
systemPrompt.ts(ex.: "Liderança com status 'Lembrança' é favorável sem compromisso — não conta como pendente de abordagem"). O rótulo na leitura vem de D3. - Semânticas que excluem "Lembrança" intencionalmente (verificar com testes, não mudar): convite de login exige
engajado(campaignInvite*), acesso ao app exigeengajado(access/contacts.ts,access/municipalities.ts), opção de "responsável" de atividade exigeengajado(activityLeadershipOptions.ts) — uma "Lembrança" não é engajada, comportamento correto.
Componentes / mudanças
leadershipSupportStatuses(src/lib/schemas/leadership.ts):['engajado', 'a_abordar', 'em_disputa', 'lembranca', 'negativo']— tipoSupportStatuseisSupportStatusderivam sozinhos.Leadership.ts(collection): opção{ label: 'Lembrança', value: 'lembranca' }na posição 4 (admin select + admin filter automáticos).- Migration:
add_lembranca_support_statusviapnpm migrate:create, SQL conforme D1. payload-types.ts: regenerar viapnpm generate:types.leadershipLabels.ts:lembranca: 'Lembrança'.SupportStatusBadge.tsx:lembranca: { variant: 'support-remembered' }.Badge.tsx+styles.css: variant e tokens (D2).getLeaderships.ts:SUPPORT_LABELS→supportStatusLabels(D3).systemPrompt.ts: linha de reconhecimento (D6).peopleData.ts:leadershipStatusesInOrdervia enum canônico (D4).- Automático (sem edição):
LeadershipListSupportStatusControl,LeadershipForm/LeadershipInternalForm/WizardLeadershipForm,leadershipListFilters/peopleListFilters,leadershipListUrl/peopleListUrl(parse enum exaustivo), omniboxes,leadershipData,wizardLeadershipContract,municipalityV2Network*, actions/routes zod (z.enum(leadershipSupportStatuses)),support-status/route.ts. - Access / Consent: nenhum — status é staff-only (
canManageCampaignStaffField), leader lockdown intocado.
Dados → forma
Não se aplica (nenhuma superfície de dado nova; valor de enum flui por filtros/facets existentes — decisão de produto do gate).
Fases verificáveis
- Schema+server — enum array + collection options + migration +
generate:types; verificação:pnpm migrate:statusepnpm migratelocal. - UI — labels + Badge + tokens +
SupportStatusBadge; verificação: badge unit test (it.each) renderizaLembrança. - IA + pessoas —
getLeadershipsDRY,systemPrompt,peopleDataordem; verificação: testes de tool e listas. - Testes — atualizar os que enumeram status (badge it.each, "every status member" do
leadershipListUrl, fixture e2e, int spec comlembrancapersistindo, pin de exclusão de pendência); NENHUM teste novo de feature em volume. - Gates —
tsc --noEmit,pnpm lint,pnpm format:check,pnpm exec knip,pnpm check:cycles,pnpm test,pnpm buildlocal.
Rabbit holes / Não escopo (engenharia)
- Migração destrutiva do enum (recreate) — desnecessária com
BEFORE. - Reordenar
a_abordarno seletor — produto não pediu. - Tratamento especial de votos para "Lembrança" — decidido no gate: campo de votos declarados já existente.
- Qualquer mudança em convites/acesso-app/atividade (engajado-only é o comportamento correto).
Riscos e mitigação
- Migration
ADD VALUE+ transação Postgres: permitido desde PG 12 dentro de transação (novo valor não usado na mesma transação); precedente shipado em prod (20260723_025513). Mitigação: verificarpnpm migratelocal emigrate:statusantes do push. - Superfície enumerada esquecida: mitigado por D3/D4 (fonte única) + teste de badge it.each + teste "every status member" da URL.
- Filtro
lembrancavazando para pendências: fail-closed já está na query; o teste unit dogetPendingLeadershipspassa a incluirlembrancana exclusão pinada.
Aceite de engenharia
- Aceite de produto da intenção ainda coberto (superfícies todas + pendências excluem + badge distinto + Sollinha reconhece)
- Invariantes AGENTS/engineering-standards (sem transações novas, sem access novo, nomes EN, rótulos pt-BR)
- Testes de domínio previstos (badge it.each, URL enum exaustivo, pin pendência, int persistence)