Commit graph

1025 commits

Author SHA1 Message Date
Francisco Solla
a7dce2c716
Merge pull request #516 from fsolla/C101-agenda-mobile-calendario-com-cara-de-app-nativo-context
C101 — Agenda mobile: calendário com cara de app nativo (contexto no header, navegação por arrasto, filtro sticky, dia na hora atual)
2026-08-09 22:43:12 -03:00
Francisco Solla
de205c61e4 style(C101): format 2026-08-09 22:23:18 -03:00
Francisco Solla
1d326a72ab fix(C101): e2e mobile mira o primeiro .activity-agenda (FC mantém grids stale em transições) 2026-08-09 22:22:12 -03:00
Francisco Solla
51fb715ec7 feat(C101): agenda mobile com cara de app nativo — contexto no header, swipe, filtro sticky, dia na hora atual
- toolbar do FullCalendar some no mobile; título do header mostra o período
  ("9 Agosto", "3–9 Agosto", "Agosto", "Agenda") e tap volta ao hoje
- navegação por arrasto horizontal (useAgendaSwipeNavigation): limiar 48px,
  claim 12px com preventDefault, touchcancel sintético contra o long-press
  do FC, dateClick do dia suprimido (C91/C15 intactos)
- filtro vira strip edge-to-edge sem rótulo, sticky sob a barra do app
  (containing block via :has no padding do scroll container)
- visão de dia com scroll interno (height fixa), 08:00 na abertura,
  cabeçalho do dia fixo, scroll preservado ao trocar de dia
- desktop inalterado; impl plan + plano pós-critique (C101-ux)
2026-08-09 22:09:47 -03:00
Francisco Solla
457aad5a21
Merge pull request #510 from fsolla/C102-mobile-sem-botao-sheet-de-sidebar-navegacao-pela-barra-
C102 — Mobile: sem botão/sheet de sidebar — navegação pela barra inferior + Mais
2026-08-09 19:56:07 -03:00
Francisco Solla
f0dff330bd feat(C102): sem hamburger/sheet de sidebar no mobile staff — navegação pela barra + Mais
No mobile (<768px) staff perde o botão de abrir sidebar na top bar
(CampaignMobileTopBar role-gated via useCampaignPageChromeRole +
isStaffCampaignRole; leader mantém) e o sheet de navegação deixa de
montar (CampaignSidebar early-return quando isMobile && staff).
Filtros salvos de Municípios (B18) migram para o drawer Mais
(MunicipalityNavSavedFilters variant 'overflow', mesma store,
apagar/desfazer/sucessão de foco; drawer ganha overflow-y-auto).

Correção de bug pré-existente (D4): o Toaster vivia dentro do wrapper
[contain:layout_paint] do painel da AI, que vira containing block para
position:fixed e stacking context próprio — toasts com ação (ex.
Desfazer) ficavam inalcançáveis sob os portais z-50 de drawer/sheet.
Movido para o root layout (campaign).

Testes: unit top bar (staff sem trigger / leader com trigger) + e2e
(staff sem trigger/dialog, leader com sheet, B18 no drawer completo).
2026-08-09 19:13:43 -03:00
Francisco Solla
27daa1339e feat(C99): toda conta campaignUser aponta para uma ficha Contact; lideranças ganham assessores responsáveis 2026-08-09 19:12:35 -03:00
Francisco Solla
7af76b9f68
Merge pull request #509 from fsolla/B183-pwa-ios-foco-em-inputs-aplica-auto-zoom-que-nao-volta-f
fix(campanha): impede auto-zoom do iOS em inputs do PWA (B183)
2026-08-09 19:10:58 -03:00
Francisco Solla
e835108324 fix(campanha): impede auto-zoom do iOS em inputs do PWA (B183)
Regra CSS única em styles.css — @media (pointer: coarse) and (hover: none)
+ [data-theme='campaign'] :is(input, select, textarea, [contenteditable]) a
16px (limiar do Safari iOS). Cobre omnibox B127, Command search, native-select
e textareas em qualquer orientação do iPhone; desktop intocado.

E2E campaignIosInputZoom: emulação iPhone 13 pina 16px na omnibox de
/campanha/municipios; control desktop pina 14px.

Closes #501
2026-08-09 18:54:33 -03:00
Francisco Solla
dff940681d fix(B182): PWA iOS — heal do zoom preso do visual viewport no standalone
Motor client-side (standalone-iOS only, no-op em aba/desktop):
heal imediato no install + launch/bfcache/resume/pós-teclado/resize,
scrollTo(0,0) + re-measure nudge, retry ≤3×250ms por episódio,
pinça preservada (janela renovada por gesture*/touch ≥2), debug
global para verificação em aparelho, fallback maximum-scale off.

40 testes unit (jsdom) pinam detecção, guards, retry e teardown.
2026-08-09 18:50:00 -03:00
Francisco Solla
3fea9e5920 style(C98): format (prettier) 2026-08-09 16:54:24 -03:00
Francisco Solla
a75228fd34 test(C98): detect CI=true (GitHub Actions) for the prod-mode e2e skip
GitHub Actions injects CI=true (not CI=1), so the full feed flow ran on
the prod build in CI and the create failed with
'NEXT_PUBLIC_SITE_URL precisa usar HTTPS em produção.' — the canonical
fail-closed rejecting the localhost test env. Accept '1' and 'true'.
2026-08-09 16:53:46 -03:00
Francisco Solla
d03da6d49a test(C98): skip full feed flow when e2e runs the prod build
CI and `pnpm push` boot the production build with
NEXT_PUBLIC_SITE_URL=http://localhost:3000, which getCampaignInviteBaseURL
rejects at runtime (fail-closed: feed URLs must be canonical public DNS),
so the generate step of the e2e can only run against the dev server. The
prod-mode CI still covers the unlocked dialog (C93 component unit) and the
filterless feed content (C93 int tests).
2026-08-09 16:20:59 -03:00
Francisco Solla
65ed945b27 feat(C98): verify calendar import link generation end-to-end (env fix + e2e)
Production feed generation failed with a generic message because
NEXT_PUBLIC_SITE_URL was missing on Vercel production: buildFeedUrl calls
getCampaignInviteBaseURL(), which throws in NODE_ENV=production without the
env (intentional fail-closed), so the action catch returned 'Não foi
possível criar o feed de calendário.' and nothing was created. The local
real-flow verification (login -> agenda without filters -> header icon ->
name -> generate -> link /campanha/agenda/ical/<secret> -> iCal 200 with
the activity) proved C92/C93 work; the divergence was the environment.
Configured NEXT_PUBLIC_SITE_URL=https://pt.jorgesolla.com.br in Production
(+ checked Preview) and redeployed.

New e2e campaignAgendaFeed pins the real flow: desktop (header -> dialog
-> generate -> link responds iCal with the fixture activity) and mobile
(FAB -> sheet -> generate). The e2e fixture now owns calendarFeed rows
(createdBy NOT NULL made the campaignUser cleanup transaction abort).

AGENTS.md deploy checklist notes NEXT_PUBLIC_SITE_URL as mandatory in
Production.

Closes #483
2026-08-09 16:20:59 -03:00
Francisco Solla
fdedfa9ad4 feat(C97): seletor shadcn de dia+horário 24h no popover de criação rápida da agenda
Troca o datetime-local nativo (AM/PM no idioma do browser, horário pouco
confiável) por ActivityDateTimeField: trigger com rótulo civil
dd/mm/aaaa às hh:mm + popover com Calendar (react-day-picker 9.7,
timeZone America/Bahia, locale pt-BR) e selects Hora 00-23 / Minuto
em passos de 15. Contrato de valor e submit inalterados; prefill do
slot mantido. Novo ui/Calendar.tsx (variante radix-nova), helpers
puros em campaignTime.ts. Sem migration. Closes #482 (PR).
2026-08-09 15:13:15 -03:00
Francisco Solla
ff53e15e7e feat(B180): gate fail-closed de leader nas tools eleitorais legadas do Sollinha (getTopDeputies/getMunicipalityVotes)
ElectionDataGate compartilhado (3 call sites) + refactor da B177 para o helper.
2026-08-09 14:54:31 -03:00
Francisco Solla
07b4509e42 feat(campaign): Tendência migra para Ações ao colapsar a coluna (B172)
Em /campanha/municipios (staff), abaixo de 60rem do container a coluna
Tendência some inteira (hidden @min-60:table-cell no th+td, invertendo a
decisão B158 'container query nunca duplica' só para Tendência) e o mesmo
editor monta como ícone compacto na coluna Ações, ao lado do de atualização;
acima de 60rem volta o badge completo e Ações é gated fechado quando nada
está oculto manualmente. Ações torna-se sempre montada para staff e pinada
por ocultação manual no B17. Uma instância visível do editor por linha por
faixa (dual-render CSS-gated, sem JS/resize). Sem migration. Closes #446.
2026-08-09 13:36:04 -03:00
Francisco Solla
4fbb92b886 fix(e2e): C95 — grid asserts com first() (FC v7 mantém grids antigos no DOM) 2026-08-09 12:42:21 -03:00
Francisco Solla
dff3a9e8b1 feat(C95): seletor de modo de visualização no header do app (agenda)
- view=week|day|month|list no contrato URL da agenda (canônico, fail-closed)
- seletor compacto 'Semana ▾' no header desktop e mobile (slot C94/C95)
- FullCalendar headerToolbar sem os 4 botões de modo; escolha vence resize
- effect normaliza a vista também quando a URL perde o param (sem vista órfã)
- useNarrowMeasured(breakpoint) em hooks/use-mobile (settle de hidratação)
- e2e reescrito no seletor + persistência + escolha-vence-resize + estabilização
  dos waits de slot (grid lazy do FullCalendar substitui gridcells no load)
2026-08-09 12:34:04 -03:00
Francisco Solla
6c34da4a47
Merge pull request #488 from fsolla/B178-salvador-cidade-agregada-no-campanha-pagina-virtual-lin
fix(B178): review do simplify — ordenação de nulls nativos, mapa sem slug da cidade, a11y e testes de borda
2026-08-09 10:53:46 -03:00
Francisco Solla
d8ee2dbafa fix(B178): review do simplify — nulls nativos em DESC, mapa sem slug da cidade, sr-only morto, sinal mobile, paginação/edges testados 2026-08-09 10:40:18 -03:00
Francisco Solla
6cf29b3810
Merge pull request #486 from fsolla/B179-sollinha-persona-no-genero-masculino-prompt-referencias
B179 — Sollinha: persona no gênero masculino (prompt + referências .md)
2026-08-09 10:22:35 -03:00
Francisco Solla
f1a8332835
Merge pull request #487 from fsolla/B178-salvador-cidade-agregada-no-campanha-pagina-virtual-lin
feat(B178): Salvador como cidade no /campanha — página virtual, linha comum na lista e busca, sem dupla contagem
2026-08-09 10:21:16 -03:00
Francisco Solla
6760b99942 feat(B179): Sollinha persona no gênero masculino (prompt + placeholder + docs) 2026-08-09 10:07:21 -03:00
Francisco Solla
de66fe3e3f feat(B178): Salvador como cidade no /campanha — página virtual, linha comum na lista e busca, sem dupla contagem 2026-08-09 10:07:05 -03:00
Francisco Solla
d5d1d0c30e chore(B181): remove o aviso de janela vazia da agenda
Sem bloco 'Nenhum compromisso nesta janela e neste filtro' nem botão
'Criar atividade' acima do calendário: a criação segue pelo slot click
(C91), botão dos filtros e FAB. buildActivityCreateHref permanece com
2 consumidores. Teste unitário de regressão + impl plan + changelog.
2026-08-09 09:34:25 -03:00
Francisco Solla
b98366ea66 test(C93): fix beforeEach import in dialog unit spec 2026-08-09 09:04:51 -03:00
Francisco Solla
2ba7a88531 feat(C93): allow generating calendar import link without filters
The 'Link de import' button was disabled whenever no agenda filter was
active, forcing a filter to subscribe to the full campaign agenda. The
server already supported the empty-filter case (filterMunicipality empty
= entire creator scope via buildFeedWhere/resolveFeedCreatorAccess), so
this unlocks the button and drops the now-dead state/hasFilters props.
The dialog copy is neutralized ('recorte' -> 'agenda') to cover both the
filtered and filterless shapes.

Int tests pin the content acceptance: coordinator filterless feed covers
all municipalities; advisor filterless feed is scoped to the administered
portfolio (nothing out-of-scope leaks); an advisor with no assigned
municipality serves an empty feed (fail-closed). A new component test
asserts the button is enabled with zero filters and the create flow works.

Closes #437
2026-08-09 09:02:06 -03:00
Francisco Solla
cd4698571a fix(e2e): first() em asserts com duplicação transitória de RSC pending (flake pré-existente, base reproduz) 2026-08-09 04:37:27 -03:00
Francisco Solla
6c631bdeec fix(e2e): agenda — waits de visibilidade para medir o slot do calendário (estabiliza inline/mais detalhes) 2026-08-09 04:36:02 -03:00
Francisco Solla
ad39602d04 feat(C94): toolbar omnibox + e2e agenda hardening + changelog 2026-08-09 04:36:02 -03:00
Francisco Solla
e0530a3a90 feat(C94): agenda omnibox + feed no header/FAB 2026-08-09 04:35:39 -03:00
Francisco Solla
663c57919f fix(B176): ordena o scope esperado do int spec (alocação não é ascendente) 2026-08-09 03:19:54 -03:00
Francisco Solla
97dd13443e test(campaign): endurece locators de texto contra duplicata transitória de hidratação
Mesmo padrão já pinado em 'Otimista: 5.000': o slot server-composed
renderiza duas cópias por um instante durante a hidratação — .nth(0)
nas asserções de perfil (webauthn) e dossiê (municípios). Não relacionado
ao B176; observado deterministicamente no servidor de produção local.
2026-08-09 03:19:54 -03:00
Francisco Solla
14bbee6e00 feat(B176): filtrar municípios por Dobradinha/Liderança/Partido + coluna Dobradinhas ao assessor
Omnibox de /campanha/municipios ganha as dimensões Dobradinha (nome do
contato), Liderança (nome do contato) e Partido (sigla), com recortes
"Sem dobradinha"/"Sem liderança"/"Sem partido" no precedente de
"Sem nível". Params aditivos stateDeputy/leadership/party na URL
congelada; buildMunicipalityListWhere ganha catálogo request-scoped
fail-closed (municipalityRelationSets) que vira as leituras reversa e
cruzada em where — sem migration, paginação nativa mantida; facets
estendidas e o mapa compartilha o catálogo. Coluna Dobradinhas liberada
ao staff (servidor já scoped B37/B157; só o gate de UI mudou).
Extrato do par sentinela+exists:false em campaignListUrl (5 call sites).
2026-08-09 03:19:54 -03:00
Francisco Solla
5a7f3bbb8a
Merge pull request #475 from fsolla/B177-sollinha-responder-em-quais-cidades-x-foi-o-deputado-ma
feat(B177): Sollinha responde em quais cidades X foi o deputado mais votado (rank competitivo reverso, flexível)
2026-08-09 02:46:17 -03:00
Francisco Solla
234c69ceef
Merge pull request #468 from fsolla/C96-feed-ical-interseptar-escopo-do-criador-no-read-defesa-e
fix(C96): intersect creator scope in calendar feed read (+ tests)
2026-08-09 02:35:56 -03:00
Francisco Solla
cef946ad7d feat(B177): Sollinha responde em quais cidades X foi o deputado mais votado (rank competitivo reverso, flexível) 2026-08-09 02:35:53 -03:00
Francisco Solla
7785c1088c fix(worktree): provisiona PLAYWRIGHT_BASE_URL no .env.test.local — e2e usa porta do slot (OPS20)
O .env.test.local (que o playwright.config.ts lê) passa a carregar
PLAYWRIGHT_BASE_URL na porta do slot (3100+slot); antes só o .env.local
(que o config não lê) o tinha, então dois test:e2e em worktrees paralelos
subiam dev servers na porta 3000. Causa-raiz tratada: os dois blocos env
inline do provision viraram um builder puro único (worktreeEnvFileContents
em scripts/lib/worktree-env.mjs) que deriva NEXT_PUBLIC_SITE_URL +
PLAYWRIGHT_BASE_URL do mesmo devPort para ambos os arquivos — paridade
estrutural, drift impossível + unit tests de paridade. playwright.config.ts
intocado (a variável certa já existia).
2026-08-09 02:19:26 -03:00
Francisco Solla
177803a54a
Merge pull request #470 from fsolla/B173-sollinha-consulta-por-voz-no-chat
B173 — Sollinha: consulta por voz no chat
2026-08-09 02:16:53 -03:00
Francisco Solla
9f997aa232
Merge pull request #471 from fsolla/B175-repintar-a-lista-de-territorios-no-padrao-da-lista-de-m
B175 — Repintar a lista de Territórios no padrão da lista de Municípios
2026-08-09 01:58:06 -03:00
Francisco Solla
4668c018bf feat(B173): consulta por voz no chat da Sollinha (STT → rascunho)
Adiciona entrada por voz ao chat: botão de mic no InputGroup grava no
browser (getUserMedia + MediaRecorder), envia o clip multipart para a nova
rota POST /campanha/api/ai-transcribe (auth cookie + rate limit no mesmo
balde + same-origin) e injeta a transcrição como rascunho editável — o
envio segue o fluxo normal do chat. O áudio nunca é persistido.

STT via Deep Infra whisper-large-v3 (DEEPINFRA_API_KEY, mesma do B174);
a costura de rede vive em utilities/ai/deepInfraTranscribe.ts; a máquina
de captura em useMicTranscript. Falha de permissão/provedor degrada a
mensagem em pt-BR sem quebrar o chat por texto.

Closes #448
2026-08-09 01:47:03 -03:00
Francisco Solla
fe81358fe3 B170 — spec do contrato do colapso de chips + shift único do load SSR deferido documentado
F1: RelationChipCell ganha spec unitário (relationChipCellCollapse) que pina o
colapso medido pelo layout real (16 chips → slice de 3 linhas = 12), expande
pelo 'Ver mais…' e recolhe pelo 'Ver menos', com getBoundingClientRect stubado
por índice — a lacuna que o B169 deixou (specs com measureOverflow:false ou
1 chip) não tinha regressão do comportamento de colapso após a mudança de timing.

F2: medição PerformanceObserver no worktree confirma o resíduo pré-hidratação
do B169 sem regressão material: load SSR→hidratação com shifts únicos
[0.139, 0.043, 0.257] (fontes do shell, nenhuma célula de chips) e ordenação
client-side com zero shifts novos; eliminá-lo exigiria estimativa de largura
(rejeitada) ou mudança de layout/regra (anti-goal) → defer documentado.

Sem migration, dados, access ou mudança visual.
2026-08-09 01:36:59 -03:00
Francisco Solla
5b342146d5 fix(C96): intersect creator scope in calendar feed read
buildFeedWhere trusted filterMunicipality without intersecting the
creator's current municipality scope when accessibleMunicipalityIds is
non-null (advisor). An advisor removed from a municipality after creating
a pinned feed kept serving its activities. Now the read re-derives the
creator's scope on every request (fail-closed, aligned with C16) and
intersects it into the where clause via the canonical
advisorMunicipalityScopeWhere fragment.

Int tests cover: advisor in-scope lists, advisor removed stops serving,
coordinator (unrestricted) unchanged. No migration (config/consulta only).
2026-08-09 00:55:11 -03:00
Francisco Solla
e65534ca9b test(e2e): atualizar marcador do território no picker de colunas (coluna Municípios saiu na B175) 2026-08-09 00:38:27 -03:00
Francisco Solla
e3f6d3f5eb
Merge pull request #457 from fsolla/C89-pagina-atualizacoes-feed-de-cards-filtro-criar-modal-bot
C89 — Página Atualizações: feed de cards + filtro + criar (modal / bottom sheet)
2026-08-09 00:05:05 -03:00
Francisco Solla
e75bb1c2f3 feat(campaignTerritories): repintar a lista de Territórios no padrão da lista de Municípios (B175)
- Colunas responsivas ao painel via @container/territory-list (P0→P3), sem scroll horizontal
- Contagem de municípios no nome ("Irecê (20)"); % e votos absorvidos na célula 2022 (hover = válidos)
- Célula 2026 no detalhe de Municípios (valor central + %, faixa de cenários no hover, leitura)
- Colunas de rede só leitura: Assessor, Liderança, Dobradinha (para todo o staff)
- Cobertura oculta por padrão no seletor; labels curtos (2022/2026/Captura/Cobertura)
- loadTerritoryOverview devolve { rows, references }; rollup puro com soma por cenário, hasEstimate e uniões de rede
2026-08-08 23:58:54 -03:00
Francisco Solla
c9134becd1
Merge pull request #456 from fsolla/C92-link-de-import-corrigir-criacao-do-feed
fix(C92): corrige criação do link de import da agenda (feed)
2026-08-08 23:39:43 -03:00
Francisco Solla
29ed733190 fix(C92): calendar feed creation stripped by admin-only system fields
createCalendarFeedLinkRecord failed with `overrideAccess:false` +
`user: campaignUser` because Payload stripped the required
`secretSlug`/`createdBy` fields (create access was admin-only) -> 400
behind the generic message; revokeCalendarFeedRecord silently no-opped
(revokedAt update access also admin-only).

- canCreateCalendarFeed now enforces advisor filterMunicipality scope
  on data (canCreateSupporter precedent) + canSetCalendarFeedSecret /
  canSetCalendarFeedRevocation permit staff to mint the secret on create
  and mark a feed revoked; secret read stays `() => false` and update
  admin-only.
- CalendarFeed stamps createdBy via stampCampaignCreatedBy; secret
  create + revokedAt update use the new staff field accesses.
- Action refactored to `*Record` (payload, actor) cores + thin wrappers;
  assertFilterMunicipalityInScope (tour precedent); console.error keeps
  the real error on the server while the friendly message stays.
- 10 int tests for create/scope/revoke/leader/secret; fixture tracks
  calendarFeed cleanup.

Closes #436
2026-08-08 23:13:30 -03:00
Francisco Solla
5a51d60c9e style(C89): formata (prettier) 2026-08-08 23:11:05 -03:00
Francisco Solla
7343f15053 feat(C89): página Atualizações — feed de cards + filtro omnibox + criar (modal/bottom sheet)
Substitui o placeholder B164 por um feed operacional da carteira (staff):
- Cards com avatar do autor, polaridade/urgente/adversário, município e data
- Filtro omnibox (município via dotted-path, polaridade, urgente, autor, busca)
  no padrão das listas (URL canônica + adapter puro)
- Criação in-place: modal desktop / bottom sheet mobile, reusando o registro
  unificado (C87) com prefill do município do filtro ativo
- Leader lockdown intacto (gate staff + canReadMunicipalityUpdate fail-closed)
- parseMunicipalityUpdateFormData único para os 3 forms de atualização
- Loader cruzando a carteira sem cláusula de município (access por ator)
- Int/unit specs (19 unit + 5 int) + convenções

Fix real: o modal de criação não pode ser filho React do <form> de filtros —
React 19 não liga progressive enhancement em action-form aninhado.
2026-08-08 23:07:23 -03:00
Francisco Solla
0a073c8e61
Merge pull request #454 from fsolla/B171-barra-inferior-mobile-folga-no-topo-rotulos-sem-sobrepo
fix(campaign): barra inferior mobile — folga no topo e rótulos sem sobreposição
2026-08-08 19:15:59 -03:00
Francisco Solla
f3d2f46a58 test(campaign): reforça e2e da barra inferior mobile após /simplify
- pina paddingTop >= 10px (pt-2.5) e cobre viewport 320px — o aceite da
  intenção promete rótulos legíveis em qualquer largura mobile.
2026-08-08 18:45:42 -03:00
Francisco Solla
2ba0cebf6f
Merge pull request #452 from fsolla/C91-criar-evento-inline-no-calendario-da-agenda-estilo-googl
C91: criar evento inline no calendário da agenda (estilo Google Calendar)
2026-08-08 18:45:27 -03:00
Francisco Solla
eb94a81ebc fix(campaign): barra inferior mobile — folga no topo e rótulos sem sobreposição
B171 #443
- CampaignBottomNav: pt-2.5 no nav (respiro da borda superior) e rótulos
  text-[10px] nowrap com gap-1 — sem encolher a área de toque.
- e2e: assert paddingTop >= 8px, rótulos < 12px e sem sobreposição horizontal.
2026-08-08 18:37:12 -03:00
Francisco Solla
c1bedcf838 test(campaignMunicipalities): torna o assert Otimista: 5.000 determinístico
O getByText sem índice também casa com o span sr-only do hover-preview quando
uma linha está hovered — strict-mode violation flaky no CI. .nth(0) fixa no
resumo da célula.
2026-08-08 18:29:42 -03:00
Francisco Solla
77673147ca feat(C91): cria evento inline no calendário da agenda (popover/drawer no slot clicado) 2026-08-08 18:27:25 -03:00
Francisco Solla
cfb27296f6 test(campaignActivity): fecha o chat antes do passo mobile da agenda (B167)
B167 migra um chat aberto no painel desktop para o drawer full-screen ao
encolher a viewport — o drawer cobriria o calendário no passo mobile deste
spec. Fecha o chat antes do resize para garantir a superfície de teste.
2026-08-08 18:11:09 -03:00
Francisco Solla
0e38d19d42 feat(B167): migra o chat Sollinha entre painel e drawer ao redimensionar
- Converse (useChat) lifts to the provider: survives desktop panel <-> mobile
  drawer swaps, internal /campanha navigation and resize (new tab still resets).
- Chat Panel/Separator always mounted (RRP panel-count change forces a full
  navigation); footprint hidden via elementRef + .b167-ai-chat-hidden
  (display:none !important), avoiding RRP's imperative collapse/expand races.
- Single 'open' flag drives both surfaces (open && viewport); reconciled with
  the panel at desktop settle and on user drag-collapse (gated by isUserInteraction).
- Mobile Drawer extracted to CampaignAIDrawer; FAB/setOpen unified; focus moves
  into the chat on the mobile->desktop swap.
- e2e campaignAiChatResize: desktop->mobile, mobile->desktop, closed stays
  closed, fresh mobile visit closed, navigation survival, new tab resets.
- Impl plan: docs/plans/migrar-chat-painel-drawer-resize-impl.md

Closes #416
2026-08-08 17:52:02 -03:00
Francisco Solla
63642483a4 feat(C90): remodela responsáveis da Atividade em campo único polimórfico
Um campo responsible (staff campaignUser · leadership · stateDeputy) multi-valor
substitui responsible/Contact + advisors + leadership; assessor mantém acesso via
notação-objeto equals no access; nova migração esquema+dados (advisors/leadership
→ responsible); busca q da lista título-only; notificação notifica staff do campo.
Closes #426
2026-08-08 17:10:48 -03:00
Francisco Solla
8b2bc51aa5
Merge pull request #433 from fsolla/plans/plan-issue
worktree: plan cria um worktree diferente por invocação (paralelismo /plan-issue)
2026-08-08 16:52:26 -03:00
Francisco Solla
6c8c466b40 fix(worktree): plan cria um worktree diferente por invocação (paralelismo /plan-issue)
Cada 'pnpm worktree plan' agora deriva um branch próprio: plans/plan-issue-<bag>
quando um bag é passado (sufixo -2/-3 em colisão) ou o próximo sequencial
plans/plan-issue-<n> quando não. O prefixo minúsculo plans/… nunca colide com o
branch <Code>-<slug> de 'next'. planBranchName (scripts/lib/worktree.mjs)
unit-testado; docs e shell/command atualizados.
2026-08-08 16:40:05 -03:00
Francisco Solla
1adf5f3d73
Merge pull request #432 from fsolla/B166-largura-padrao-do-chat-sollinha-com-teto-no-desktop
feat(B166): largura padrão do chat Sollinha com teto de 360 px e resize livre persistido
2026-08-08 16:24:30 -03:00
Francisco Solla
441c8d1156 refactor(B166): endurece e2e (poll determinístico) e clamp exato após revisão 2026-08-08 16:04:54 -03:00
Francisco Solla
c399b918c3 feat(worktree): comando plan — worktree dedicado ao /plan-issue
Novo subcomando `pnpm worktree plan [--go] [--no-migrate]` cria/reutiliza o
worktree de planejamento no branch fixo `plans/plan-issue`
(`PLAN_WORKTREE_BRANCH`), sem vínculo com a fila de claim: um `next` posterior
da próxima Issue nunca colide em branch nem em slot (slot derivado por hash do
branch, bumpado entre worktrees vivos). Mesmo provisionamento isolado do
`next` — porta 3100+slot, bancos teqo_wt<slot>/teqo_wt<slot>_test, migrations.

- scripts/worktree.mjs: cmdPlan + provision() com purpose/issue opcionais
- scripts/lib/worktree.mjs: constante PLAN_WORKTREE_BRANCH
- tests/unit/worktree.unit.spec.ts: derivação de slot do plano
- docs: .opencode/commands/worktree.md, shell, skills, AGENTS.md, changelog
2026-08-08 16:00:27 -03:00
Francisco Solla
2ca1afa6a7 feat(B166): largura padrão do chat Sollinha com teto de 360 px e resize livre persistido 2026-08-08 15:32:07 -03:00
Francisco Solla
ec0b109b5a
Merge pull request #430 from fsolla/B168-desfazer-encadeamento-automatico-das-acoes-rapidas-do-w
B168 — Desfazer o encadeamento automático das ações rápidas do wizard
2026-08-08 14:30:27 -03:00
fsolla
7faf3ed27f feat(campanha): B168 — desfazer encadeamento automático das ações rápidas do wizard
Ações de ajuste (votos, tendência, atualização, liderança) agora retornam à
origem ao salvar (toast + from allowlist B110 / Início), sem avançar sozinhas
para outro wizard. Removidos fila de encadeamento (wizardActionChain.ts),
param entry, skip/Pular (chrome mobile sempre X) e prefill de nota sem
produtor. Liderança: botão pós-save 'Continuar' → 'Concluir' (origem).
Sem migration.
2026-08-08 14:10:47 -03:00
Francisco Solla
bd06018bde B164: barra de navegação inferior no mobile para staff
Mobile bottom nav (staff only): 5 items (Início, Municípios, Atualizações,
Agenda, Mais) with Drawer overflow for secondary destinations. Leader and
desktop remain sidebar-only. FAB + scroll padding adjusted for safe-area.

Resolves #400
2026-08-08 12:57:05 -03:00
Francisco Solla
9447686edf
Merge pull request #421 from fsolla/worktree-envs-isoladas
feat(scripts): worktree next provisiona ambientes isolados (porta + bancos + migrations)
2026-08-08 11:51:46 -03:00
Francisco Solla
25773d0296
feat(C16): add calendar feed sync by filter with Google Calendar import link (#419)
- Add calendarFeed collection with secret slug credential
- Add iCal feed endpoint at /campanha/agenda/ical/[secret] (unauthenticated)
- Add server actions for creating and revoking calendar feeds
- Add CalendarFeedButton UI component in agenda filters bar
- Add unit tests for iCal generation
- Fail-closed access: feed re-evaluates creator's municipality scope on each read
- Leader lockdown preserved; no PII in feed output
2026-08-08 14:47:17 +00:00
Francisco Solla
8125334401 feat(worktree): ambientes isolados por worktree — porta própria, bancos próprios, migrations no provision
pnpm worktree next [--go] [--no-migrate] agora provisiona um ambiente
determinístico por worktree: slot = parte numérica do código da Issue
(C15→15; hash sem dígitos; bump em colisão entre worktrees vivos), dev
server em 3100+slot, bancos teqo_wt<slot> / teqo_wt<slot>_test no
container compartilhado (-p teqo) com a cadeia de migrations aplicada;
escreve .env.local + .env.test.local (gitignored, marcador gerado; envs
manuais nunca sobrescritos) e copia PAYLOAD_SECRET/Blob/VAPID do main.
kill remove o worktree e dropa os bancos gerados (WITH FORCE, best-effort).

Suites leem .env.test.local depois de .env.test (vitest.setup /
playwright / db-doctor / gate-ci espelham o layering) e
assertTestDatabase aceita teqo_test ou teqo_<worktree>_test em localhost
só (fail-closed preservado). db:start/db:stop fixam -p teqo (um
container de qualquer diretório); max_connections 200→400. Sem Docker
(Cursor Cloud): degrada ao compartilhado com aviso.

Derivação pura em scripts/lib/worktree-env.mjs (unit-tested); fluxo
validado end-to-end: provision → migrate → 598/598 int no banco do
worktree → kill sem resíduo.
2026-08-08 11:33:07 -03:00
Francisco Solla
c5001b5005
Merge pull request #410 from fsolla/C87-atualizacao-unificada-texto-polaridade-urgente
feat(campaign): C87 — atualização unificada (texto + polaridade + urgente)
2026-08-07 07:20:28 -03:00
Francisco Solla
11a9f45d69 test(e2e): fix C87 unified-update specs (polarity + adversary signal) 2026-08-07 06:56:41 -03:00
Francisco Solla
212a1f2423 feat(campaign): C87 — atualização unificada (texto + polaridade + urgente)
Substitui o modelo de MunicipalityUpdate (kind/semanal/urgente/nota/sinal + signalType)
por um modelo unificado: body obrigatório + polarity (boa/neutra/ruim) + urgent + adversarySignal.

- Schema: polarity enum obrigatório, urgent boolean default false, adversarySignal boolean
- Wizard: novo WizardUpdateBodyStep com campo de texto + polaridade + urgente
- Lista: controle unificado de atualização com polaridade inline
- Feed: exibe polaridade como badge colorido + indicador de urgência
- V2 status strip: atualização rápida de polaridade
- E11: queries atualizadas para polarity + adversarySignal
- Migração: 20260807_030000_unify_municipality_update (backfill polarity=neutra, urgent de kind, adversarySignal de kind+signalType)
- Testes: 18/18 integração C87, 593/593 total, 1399/1399 unitários
- Fix: testes de state deputy usavam nomes fixos que colidiam entre execuções
2026-08-07 05:04:41 -03:00
Francisco Solla
55178247c8
Merge pull request #409 from fsolla/C15-fullcalendar-em-campanha-agenda
feat(campanha): C15 — FullCalendar em /campanha/agenda
2026-08-07 04:56:52 -03:00
Francisco Solla
59983b33f7 feat(campanha): C15 — FullCalendar em /campanha/agenda
Issue #390 — entrada operacional staff com FullCalendar v7 route-local
(semana/mês/lista), filtros compartilháveis por município/deputado/tag,
criação pelo slot, detalhe por URL e remarcação drag/resize sob RBAC,
transação e lock por atividade.

- /campanha/atividades e giros permanecem como lista/fluxos legados
- Sem migration/collection nova; reutiliza activity de C14
- Fuso Bahia, range exclusivo, erro/revert, loading/empty testados
- Leader negado, advisor limitado pelo access atual
- E2E cobre filtros, drag, mobile 390x844 e slot prefill
- Int specs cobrem RBAC, concorrência e defense-in-depth

Impl: docs/plans/fullcalendar-agenda-campanha-impl.md
2026-08-07 04:37:36 -03:00
Francisco Solla
b61f1ec36a style(tests): prettier-format worktree unit spec 2026-08-07 01:27:43 -03:00
Francisco Solla
1c0648d59a feat(scripts): deterministic worktree + issue CLI (next --go, kill, all) 2026-08-07 01:27:22 -03:00
Francisco Ignácio Fontoura Solla
c1fb8c03c4 test(e2e): document municipality locator boundary 2026-08-06 19:12:32 -03:00
Francisco Ignácio Fontoura Solla
9c99469c36 test(e2e): stabilize municipality campaign journeys 2026-08-06 19:00:13 -03:00
Francisco Ignácio Fontoura Solla
48623b0d06 fix(campanha): preserve StateDeputy migration identity 2026-08-06 18:38:47 -03:00
Francisco Ignácio Fontoura Solla
033c270a89 feat(campanha): B163 — editar contatos das dobradinhas 2026-08-06 16:52:55 -03:00
Francisco Ignácio Fontoura Solla
30de02fd89 fix(e2e): use correct submit label 'Criar atividade' from nova page 2026-08-06 06:09:11 -03:00
Francisco Ignácio Fontoura Solla
86ceeaaf00 fix(e2e): simplify activity E2E (remove tag input interaction) 2026-08-06 05:54:41 -03:00
Francisco Ignácio Fontoura Solla
39c5e931ca fix(e2e): update activity E2E for C14 tags form (no kind/origin) 2026-08-06 05:36:38 -03:00
Francisco Ignácio Fontoura Solla
adc29c0379 feat(campaign): C14 remodel activity for agenda paradigm (#389)
- Replace kind enum with free-form tags (text[] hasMany)
- Remove origin and deadline fields
- Status: remove rascunho/planejado, keep confirmado/realizado/cancelado
- startAt required for user-facing creates (optional for tour drafts)
- Add deputyPresent time gate (only coordinator/candidate can reschedule)
- UI: calendar-like form with tag input, no origin/deadline, no draft status
- E13 giros: tourStopRoleActivityKind → tourStopRoleActivityTags
- Migration: kind→tags with label mapping, status remap, enum recreation
- Activity texts table creation (was missing from migration history)

Closes #389
2026-08-06 05:20:05 -03:00
Cursor Agent
5f97b6a21c
feat(ai): add buildCampaignLinks tool for Sollinha navigation (B162)
Introduce a closed-catalog URL builder that delegates to existing list/detail
href helpers, with role-based guards for leader lockdown and unrestricted-only
assessor routes. Register the tool in the AI chat and teach the system prompt
when to offer markdown links after resolving entity ids/slugs.

Closes #383
2026-08-06 00:22:11 +00:00
Francisco Solla
8abf18ecf9 test: stabilize responsive municipality journeys 2026-08-05 15:00:33 -03:00
Francisco Solla
2f8d16881d test: align municipality e2e journeys with responsive columns 2026-08-05 14:24:47 -03:00
Francisco Solla
cc60b82245 fix: force container width past 48rem in iPad test to reveal the table 2026-08-05 12:32:05 -03:00
Francisco Solla
a36b0b2625 simplify: safe fallback for browsers without container queries, trim test overhead 2026-08-05 12:28:17 -03:00
Francisco Solla
150bea2621 feat: make municipality columns follow content width 2026-08-05 11:35:20 -03:00
Francisco Solla
423c61d40b feat: distinguish municipality column defaults from explicit visibility 2026-08-05 10:49:58 -03:00
Francisco Ignácio Fontoura Solla
9fa701633e feat(campaign): unify municipality relation editors 2026-08-05 02:01:40 -03:00
Francisco Ignácio Fontoura Solla
896d957497 chore: migrate .cursor/ references to .agents/ folder
Replace all project-local .cursor/ path references with .agents/ across
rules, skills, environment config, hooks, and ignore files. Keep Cursor
editor paths (~/.cursor/, window.cursor, CURSOR_API_KEY) and vendored
Impeccable editor-hook paths unchanged.
2026-08-05 00:09:09 -03:00
Francisco Solla
4b79f00125
Merge pull request #373 from fsolla/feature/ai-chat-sollinha
feat: AI chat 'Sollinha' — campaign data assistant (v1 read-only)
2026-08-04 06:26:48 -03:00
Francisco Ignácio Fontoura Solla
f9cd1df7b2 feat: AI chat 'Sollinha' — campaign data assistant (v1 read-only)
- Streaming AI endpoint at /campanha/api/ai-chat with DeepSeek V4 Flash
- 8 tools: calculate, getMunicipalityVotes, getTopDeputies, getDobradinhas,
  getMunicipalityOverview, getLeaderships, getOrganizations, searchEntities
- In-memory rate limiter (50 msgs / 15 min per user)
- Desktop: resizable right sidebar via react-resizable-panels
- Mobile: full-screen Drawer opened by FAB (2/3 size of quick-actions FAB)
- Markdown rendering with react-markdown + remark-gfm (tables, bold, lists)
- Session-bound context (clean slate each open)
- Auth via existing campaign-token cookie, tools respect user RBAC
- Architecture doc: docs/plans/ai-chat-sollinha.md
- E2E tests: campaignAiChat.e2e.spec.ts (mocked endpoint)
2026-08-04 06:17:31 -03:00
Francisco Ignácio Fontoura Solla
6b8864debb B156 — assessores por dobradinha (campo, coluna e seção; RelationOptionCell extraído) 2026-08-04 04:57:15 -03:00
Francisco Ignácio Fontoura Solla
9780bb23df style: format campaignComponents unit spec 2026-08-04 02:53:44 -03:00
Francisco Ignácio Fontoura Solla
086a9c8ee0 B157 — coluna de dobradinhas na lista de municípios (+ criação inline) 2026-08-04 02:53:43 -03:00
Francisco Ignácio Fontoura Solla
596085c977 feat(campanha): B155 — coluna de lideranças na lista de municípios
Espelho da coluna de Assessores: chips com nome do contato, popover/sheet
com Command + delta otimista e criação inline (Nome + Celular). Sem
migration — leitura reversa de leadership.municipalities no bundle e
escrita reusando as actions B34/wizard; rota POST
/campanha/municipios/leaderships via campaignJsonMutationRoute.
2026-08-04 01:49:55 -03:00
Francisco Ignácio Fontoura Solla
b74268714c C86+ — simplify: JSDoc do contrato home, testes de borda e nota no impl plan
Fixes do /simplify (3 revisores read-only): JSDoc de normalizePathname/isListPath
documentando o invariante (homes nunca são '/' nem terminam em slash), teste de
multi-slash e inputs degenerados, comentário de isCampaignHomePath encurtado.
Sem mudança de comportamento.
2026-08-04 01:17:53 -03:00
Francisco Ignácio Fontoura Solla
4a90fb19ec C86+ — pathname de lista das ações rápidas em fonte única (isListPath)
Extrai isListPath + normalizePathname exportados em campaignQuickActionPaths.ts
e migra os 9 wrappers (7 públicos + 2 privados) + a 3a cópia de normalize em
campaignPageChrome.ts. Refactor estrutural sem mudança de comportamento;
derivação de regex de detalhe numérico adiada (gatilho: 3o vertical [id]).
Inclui docs/plans/escala-dry-pos-acoes-rapidas-impl.md e o fix de format
pré-existente de docs/plans/liderancas-coluna-municipios.md (bloqueava format:check).
2026-08-04 01:14:35 -03:00
Francisco Ignácio Fontoura Solla
5a52bb0efa fix(int): batch B19 usa municípios alocados — remove raça com cap de assessores
O teste 'assigns and removes multiple municipalities in one batch' escolhia o
segundo município com id not_equals cru; sob ~48 workers paralelos podia cair
num município que um spec paralelo (cap 10 do B27/B154) acabara de encher,
fazendo o batch estourar o teto. getMunicipality() aloca município
globalmente único e purga resíduo.
2026-08-03 23:13:01 -03:00
Francisco Ignácio Fontoura Solla
26b2205c3a B154 — criar assessor inline no popover da lista de municípios
Closes #352

- POST /campanha/municipios/advisors aceita body de criação (name) na union;
  createMunicipalityAdvisorRecord cria campaignUser advisor (stub
  <slug>@criado.invalid, senha aleatória) e atribui ao município na mesma
  transação (cap 10 com rollback, lock municipality-advisors:{id})
- MunicipalityAdvisorCreateProvider compartilha o assessor criado entre linhas;
  opção "+ Criar assessor" e chip otimista com id temporário no control
- advisorNameSchema unificado em primitives.ts; predicado de placeholder cobre
  @criado.invalid (reset de senha e célula de e-mail tratam stubs inline)
- Destrava o spec e2e de municipios (falhas pré-existentes em main): key
  warning do fragment trailing de MunicipalityFilters; settle-wait da animação
  do Drawer no teste B136
2026-08-03 22:46:06 -03:00
Francisco Ignácio Fontoura Solla
cf09cb548d format: Prettier 2026-08-03 21:06:52 -03:00
Francisco Ignácio Fontoura Solla
d200a1b012 B86 — ações rápidas — apoiadores (staff): catálogo no FAB/overlay 2026-08-03 21:03:13 -03:00
Cursor Agent
607eecd12b
B153 — simplify: phone assert helper, displayValue interno
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 14:09:14 +00:00
Cursor Agent
bb4c1ab199
B153 — editar nome, e-mail e celular da liderança (lista + detalhe)
- leadershipContactUpdateSchema + updateLeadershipContactRecord
- LeadershipContactFieldControl (lápis → input → blur-save)
- Lista e bloco Contato no detalhe
- Int tests para update, conflito de telefone e escopo

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 14:02:59 +00:00
Cursor Agent
fb75f7235b
fix(P5-P1): schedule campaign notification push after txn commit
queueMicrotask under req.transactionID raced withPayloadTransaction
commit. Register after-commit callbacks, soft-fail missing notification
rows on send, and pin the schedule with a convention unit spec.
2026-08-03 04:40:38 +00:00
Cursor Agent
30a5a8a5e3
B151 — FAB secundário no detalhe município v2 (#334)
- Catálogo v2 de ações rápidas: dossiê, eleições, nova liderança, giro
- Integra no chassis B126 via registry + context sync na rota v2
- Omitido registrar sinal (já na faixa de status B147)

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 01:03:10 +00:00
Francisco Solla
2414001606
Merge pull request #343 from fsolla/cursor/pool-i333-5914f625-bd1e
B150 — Município v2 bloco Agora (encaminhamento + sugestão + visita)
2026-08-02 21:53:38 -03:00
Francisco Solla
d134dd0462
Merge pull request #346 from fsolla/cursor/pool-i315-94cacb95-bf11
B145 — Detalhe de entidade — título no header (sem hero espaçoso)
2026-08-02 21:52:20 -03:00
Francisco Solla
7753470a3b
Merge pull request #345 from fsolla/cursor/pool-i296-f91a8b70-0be4
OPS18 — Promote plan-issue dual (agente + Action no merge)
2026-08-02 21:45:56 -03:00
Cursor Agent
ffe7eca629
test(e2e): assert assessoria on dossiê tab after B145 hero removal 2026-08-03 00:29:48 +00:00
Cursor Agent
c62fe67809
test(e2e): assert entity identity in shell chrome (B145) 2026-08-03 00:29:48 +00:00
Cursor Agent
5696aef9ed
B145 — move entity identity to shell header on detail pages
- Detail routes return null from campaignPageChrome catalog (override per page)
- SetCampaignPageChrome + generateMetadata on 8 entity detail routes
- Remove body hero (h1, Voltar links, duplicate identity chips)
- Keep operational badges/chips in page body
- Update zone map e2e to assert shell chrome title slot

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 00:29:48 +00:00
Cursor Agent
debfd37919
style: prettier B150 files 2026-08-03 00:28:54 +00:00
Cursor Agent
6955cccc6b
B150 — Município v2 bloco Agora (encaminhamento + sugestão + visita)
- Loader e seção Agora na rota /campanha/municipio/[slug]/v2
- Encaminhamento editável com auto-save via POST /municipios/next-steps
- Até 2 sugestões E11 com resolve/dispensa existente; silêncio explícito
- Visita E13 condensada em uma linha + link ao compositor de giros
- Revalidação v2 ao resolver sugestão; helper compartilhado de empty state E11

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 00:28:47 +00:00
Francisco Solla
deefe566b0
Merge pull request #341 from fsolla/cursor/pool-i332-0d1ca390-b998
B149 — Município v2 — rede (lista edit-in-place)
2026-08-02 21:24:44 -03:00
Cursor Agent
7665b1062c
fix(ops): OPS18 simplify — Related regex, already-ready, job gate
Aperta parseRelatedIssueNumbers (anti hyphen), canPromotePlanIssue
already-ready, workflow só com Related # no body + PR_BODY, soft-fail
por Issue no Action script.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 00:22:55 +00:00
Francisco Solla
90bbfeb2da
Merge pull request #339 from fsolla/cursor/pool-i302-2f4dd995-03ba
B136 — Início / FAB — grade 3 colunas × 2 linhas (corrigir orientação)
2026-08-02 21:20:55 -03:00
Cursor Agent
b7f4e16c0f
feat(ops): OPS18 — Action promote Related #N no merge
Safety net dual com agent:ready: no merge em main, workflow lê Related #N
e promove Issues ainda aguardando plano (canPromotePlanIssue), soft-skip
idempotente. Helper parseRelatedIssueNumbers + pins unitários.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 00:20:48 +00:00
Francisco Solla
1266b47b0e
Merge pull request #336 from fsolla/cursor/pool-i292-298f3db9-b2f7
OPS17 — Ciclo de vida plan-issue — colaboração fechada antes de claim
2026-08-02 21:17:03 -03:00
Francisco Solla
43dcd8da9b
Merge pull request #325 from fsolla/cursor/pool-i306-4a43a35f-79fc
B138 — Atividades: presets e busca na omnibox; remover abas
2026-08-02 21:14:02 -03:00
Cursor Agent
c95d88562e
format + e2e prewarm for pledge autosave routes 2026-08-03 00:11:22 +00:00
Cursor Agent
7d89356710
B149 — Município v2 rede: lista edit-in-place com top-N lideranças
- Seção Rede na rota v2 com tabela/lista densa e CTAs Ver todas / Nova
- Células autosave (B9) para declarado e estimado via JSON routes
- Loader + view puro com ordenação por estimado/declarado
- Revalidate v2 em writes de pledge; unit tests de merge/sort

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 00:10:30 +00:00
Cursor Agent
d2cc1d2693
feat(ops): OPS17 — plan-issue blocked até plano em main
Register com --plan nasce blocked (não claimável); pnpm agent:ready
promove após o plano em main. Gate da skill plan-issue endurecido.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-03 00:06:14 +00:00
Cursor Agent
0d124b47cb
fix: restore searchOnlyListOmnibox exports from main (CI merge) 2026-08-03 00:01:48 +00:00
Cursor Agent
a2e952b3a2
fix(B136): resolve listOmniboxB128 merge conflict markers for CI 2026-08-02 23:53:33 +00:00
Cursor Agent
a2553e1f03
merge main into B147; keep optional sinal body for v2 status
Align with B134 engagement write path; preserve B147 sinal empty-motivo
(collection + zod) required by the status-strip confirm rite.
2026-08-02 23:48:29 +00:00
Francisco Solla
e3a54be217
Merge pull request #337 from fsolla/cursor/pool-i313-f30f822f-8e1d
B144 — Sidebar — entrada para Organizações
2026-08-02 20:38:36 -03:00
Cursor Agent
764343da8e
test(B128): cover search-only omnibox exports for knip graph 2026-08-02 23:36:37 +00:00
Cursor Agent
18c1317884
chore(B136): keep groupActionsByRow internal; drop unused import 2026-08-02 23:36:28 +00:00
Cursor Agent
4cbb65963b
fix(B136): pass raw boxes to grid geometry assert 2026-08-02 23:36:28 +00:00
Cursor Agent
3ac5d303e8
test(B136): assert 3×2 grid by column clusters (tolerant row heights) 2026-08-02 23:36:28 +00:00
Cursor Agent
993b9af69b
style: format B136 files with Prettier 2026-08-02 23:36:28 +00:00
Cursor Agent
2f2c8563a5
B136 — corrigir grade 3×2 no Início e FAB overlay
- layout=grid no botão com w-full/min-w-0 sem box-content no mobile
- li min-w-0, gap-2, data-layout=grid-3 no strip
- e2e com geometria (boundingBox) em viewport 390
- impl plan docs/plans/inicio-acoes-grade-3cols-2rows-impl.md

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:36:28 +00:00
Cursor Agent
ce44ec1949
chore: drop unused search-only omnibox exports; finish B147 simplify notes
Knip: remove dead suggestions/remove/clear helpers after organizações moved
to a typed omnibox. Land deferred-helper note + empty-sinal int coverage.
2026-08-02 23:35:22 +00:00
Cursor Agent
6b8aa8b6cf
fix(campanha): B147 simplify — sinal empty body + cold select
Allow empty motivo on sinal through collection beforeValidate; distinguish
warm-typeless vs cold in the v2 signal select; reuse list signal form action.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:33:46 +00:00
Cursor Agent
c66ffd598c
fix(campanha): map municipio v2 route in e2e affected manifest
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:30:51 +00:00
Cursor Agent
0aa47f8673
feat(campanha): B147 município v2 shell + faixa de status
Rota paralela /campanha/municipio/<slug>/v2 com selects de nível, tendência e
sinal (modal de motivo opcional), classe territorial e agregado. Soft-dep B134:
motivo opcional no movimento de nível e remoção do campo “voltar atrás” do
write path/lista.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:29:05 +00:00
Cursor Agent
4ea2085239
B138 — simplify: min-length e normalização da busca
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:26:48 +00:00
Cursor Agent
f0a7758eb3
B138 — atividades: presets e busca na omnibox; remove abas
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:26:48 +00:00
Cursor Agent
7fe42a5fa5
test(omnibox): cover search-only helpers for knip graph (#313)
Knip could not see exports only referenced from client filter components;
extend B128 unit spec so CI knip stays green.
2026-08-02 23:25:50 +00:00
Francisco Solla
6539d0cc55
Merge pull request #329 from fsolla/cursor/pool-i288-97b0de1d-f857
B134 — Motivo opcional (tendência + nível); remover voltar atrás do DB
2026-08-02 20:25:23 -03:00
Francisco Solla
466a34525d
Merge pull request #328 from fsolla/cursor/pool-i317-1bee052c-c52f
B146 — FAB overlay idle on open; close/handle chrome
2026-08-02 20:23:54 -03:00
Francisco Solla
8a60b6f0f7
Merge pull request #326 from fsolla/cursor/pool-i310-73006baf-d8e9
B142 — Assessores — filtrar por município da carteira na omnibox
2026-08-02 20:22:16 -03:00
Francisco Solla
56b96198cb
Merge pull request #324 from fsolla/cursor/pool-i309-84ab81f4-8444
B141 — Apoiadores — filtro por fonte na omnibox
2026-08-02 20:20:40 -03:00
Francisco Solla
66c84a9f6b
Merge pull request #321 from fsolla/cursor/pool-i289-de3861a0-5cd2
B135 — Wizard Voltar = passo imediatamente anterior na cadeia
2026-08-02 20:19:52 -03:00
Francisco Solla
c90c7d3722
Merge pull request #323 from fsolla/cursor/pool-i311-f0d9725a-dfcb
B143 — Lideranças — filtrar por organização e dobradinha na omnibox
2026-08-02 20:19:41 -03:00
Francisco Solla
4448d6c80b
Merge pull request #322 from fsolla/cursor/pool-i308-39f49212-ad42
B140 — Demandas omnibox: tipo e busca
2026-08-02 20:19:17 -03:00
Cursor Agent
ea6641071e
test(nav): reuse ORGANIZATIONS_LIST_PATH in sidebar spec
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:15:18 +00:00
Cursor Agent
0dc3d22646
feat(nav): add Organizações to staff sidebar (B144, #313)
Insert Organizações after Lideranças in staffNav so staff can discover
the existing /campanha/organizacoes list from the structural nav.
Leaders remain on leaderNav; isCampaignNavActive already covers list,
detail, and /nova paths.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:13:34 +00:00
Cursor Agent
974907f023
Format migration unit spec 2026-08-02 23:10:20 +00:00
Cursor Agent
b931092320
B142 — format + impl plan deferred debts note 2026-08-02 23:09:01 +00:00
Cursor Agent
f40957f101
B134 — allow empty rationale on movimento allocation decisions
- Relax AllocationDecision.rationale required flag; validate in hook
- Fix empty-motivo int test to use first movement from null

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:08:52 +00:00
Cursor Agent
fced1f3e1d
B146 — focus dialog title on open (B42 pattern); consolidate mobile test stub
- Move initial focus to sr-only title instead of void/body (a11y).
- Fold matchMedia mock into stubMobileViewport helper.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:07:29 +00:00
Cursor Agent
7f17a7db79
fix(campanha): B140 wire onCommitQuery + simplify import hygiene
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:06:48 +00:00
Cursor Agent
4d4764893e
B142 — filtrar assessores por município da carteira na omnibox
Adiciona dimensão municipality na URL e omnibox da lista de assessores,
espelhando o precedente de lideranças: chips removíveis, busca tipada no
catálogo e filtro server OR via municipality.advisors.

Closes #310

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:05:33 +00:00
Cursor Agent
989e3abab8
Format B135 wizard back navigation files 2026-08-02 23:05:09 +00:00
Cursor Agent
c9fdc59ed2
B143 — omnibox de lideranças filtra por organização e dobradinha
- URL state: organization / stateDeputy (OR inclusivo por dimensão)
- Facets cross-filtered em leadershipData
- Adapter omnibox + wiring na página
- Testes unit/int e plano de implementação

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:04:45 +00:00
Cursor Agent
56e60ce589
B146 — stub innerWidth in mobile overlay unit tests
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:04:32 +00:00
Cursor Agent
28525aab4b
B135 — Voltar no wizard usa passo lógico anterior na cadeia (#289)
- wizardPreviousHref em wizardActionChain para todos os elos B98
- Avanço encadeado com router.push; Voltar no chrome com replace
- Testes unitários para href anterior e navegação pending

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:04:26 +00:00
Cursor Agent
e731e407ab
style: prettier format B139 files 2026-08-02 23:04:25 +00:00
Cursor Agent
59206574e6
Fix int test marker helper call (fixtures.value)
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:04:25 +00:00
Cursor Agent
e8f21a9885
B140 — Demandas omnibox: tipo e busca na lista
- Add q param to demand list URL contract and buildDemandListWhere
- Extend demandOmnibox with kind + search chips/suggestions/actions
- Search title and leadership contact name (visible list columns)
- clearDemandOmnibox drops bar filters but keeps activity deep-link
- Unit + int tests; impl plan docs/plans/demandas-omnibox-tipo-busca-impl.md

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:03:24 +00:00
Cursor Agent
b509c7d7b3
B141 — filtro por fonte na omnibox de apoiadores (#309)
Adiciona dimensão Fonte ao estado de lista, where-builder, aggregate SQL
e adapter omnibox (B128), com guard isSupporterSource e testes.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:03:22 +00:00
Cursor Agent
23e71d2de3
B146 — FAB overlay idle on open; close/handle chrome fixes
- Block Dialog onOpenAutoFocus and Drawer initialFocus so search does not
  steal focus on open (actions stay visible per B132 idle state).
- Reserve md:pr-10 on search chrome so desktop close button does not overlap.
- Enable showSwipeHandle on mobile drawer (no X button).
- Unit tests for idle state, swipe handle, and close spacing.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:03:04 +00:00
Cursor Agent
5335f69b43
B134 — motivo opcional (tendência + nível); remove reversalSignals
- Make engagement level note optional in schema and UI
- Remove reversalSignals field from popover, action snapshot and DB
- Add data migration to strip reversalSignals from movimento snapshots
- Wizard trend note step no longer requires text; labels show (opcional)

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:02:53 +00:00
Cursor Agent
68ab54756e
B139 — tipo na omnibox de organizações
Adapter organizationOmnibox (q + kind exclusivo), OrganizationFilters
migrado do searchOnly degenerado, testes unit B128.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 23:02:46 +00:00
Francisco Solla
345db9474f
Merge pull request #287 from fsolla/cursor/pool-i283-a9b2f259-fcc3
B133 — Listas /campanha — subtítulos curtos (sem prosa; filtro salvo em Municípios)
2026-08-02 18:33:46 -03:00
Francisco Solla
285caba300
Merge pull request #286 from fsolla/cursor/pool-i280-093296a2-744e
B132 — Ações rápidas — grade 3×2 (Início + FAB) e polimento do overlay
2026-08-02 18:23:29 -03:00
Cursor Agent
f89fc4b81f
feat(campanha): B133 — listas sem subtítulo de prosa + filtro salvo no header
- Remove prose subtitles from list routes, Quadro and create/edit chrome
- Keep subtitles on Organizações, Conceitos, Perfil, Contatos (out of scope)
- MunicipalityListPageChrome: saved filter name as header subtitle when URL matches
- Edit pages: title-only chrome override
- Impl plan: docs/plans/listas-sem-subtitulo-prosa-impl.md

Closes #283

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 21:16:46 +00:00
Francisco Solla
1771c5c2c4
Merge pull request #282 from fsolla/cursor/pool-i265-cbfa8dec-92a1
B128 — Adotar barra omnibox nas demais listas /campanha
2026-08-02 18:12:49 -03:00
Cursor Agent
8d30667c2a
B132 — grade 3×2 no Início/FAB overlay e polimento do drawer
- CampaignHomeActionStrip: grid-cols-3 no mobile; variante grid para overlay
- CampaignQuickActionsOverlay: grade no lugar da strip; altura por conteúdo;
  pt-4 na busca focada; limpa query ao fechar
- CampaignQuickActionsHost: clear() em fechamento e mudança de rota
- Testes unit atualizados; impl plan em docs/plans/

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 21:08:09 +00:00
Cursor Agent
f59d4a6736
chore: format + e2e manifest mappings for B128 omnibox modules 2026-08-02 20:56:12 +00:00
Cursor Agent
0903576f9e
B128 — Adotar barra omnibox nas demais listas /campanha
Estende o chassis B127 (CampaignListOmnibox + adapters puros por domínio)
para territórios, lideranças, dobradinhas, apoiadores, atividades, demandas,
organizações e assessores. Remove pilhas antigas (search debounced, collapsible,
mobile selects, CampaignFilterChips). URL contracts extraídos para módulos
client-safe onde necessário (demand/organization/advisor). Testes unitários
nos adapters + hook navigate-only.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 20:55:12 +00:00
Francisco Ignácio Fontoura Solla
0baeda22a0 Fix e2e failures from FAB chrome waits and B27 advisor strict-mode.
B126 mobile specs looked for a page heading that shell chrome replaced;
wait on campaignPageChrome with exact title text, and wrap the desktop
title so subtitle no longer collides. Pin B27 to the município-specific
assessores trigger and map home-search libs to campaignMunicipalities.
2026-08-02 17:49:08 -03:00
Cursor Agent
02c4843c5c
fix(e2e): restore chrome title slot locator; drop unused scenario hook
Ensure campaignPageChrome targets campaign-page-chrome-title after rebase,
and remove useMunicipalityEstimateScenario orphaned by B129 overview removal.
2026-08-02 20:32:06 +00:00
Cursor Agent
a68e1d8efb
fix(e2e): target chrome title slot so subtitle does not collide
Desktop chrome put title+subtitle in one text node; exact getByText
failed. Mobile subtitle matched Municípios case-insensitively. Mark the
title with data-slot and assert against that node.
2026-08-02 20:31:18 +00:00
Cursor Agent
8e9864362b
fix(e2e): exact match in campaignPageChrome for mobile subtitles
Mobile top bar puts title + subtitle in the same chrome slot; substring
getByText('Municípios') also hit 'Os 435 municípios…'.
2026-08-02 20:31:07 +00:00
Cursor Agent
62673367c9
test(e2e): harden município list selectors after B127 omnibox
Use campaignPageChrome for FAB specs and .first() on Editar assessores
when mobile+desktop cells both mount.
2026-08-02 20:30:55 +00:00
Cursor Agent
d64b700872
test(e2e): assert municipality omnibox instead of Ordenado por label 2026-08-02 20:30:55 +00:00
Cursor Agent
3c993463bd
style: prettier municipalityOmnibox unit spec 2026-08-02 20:30:55 +00:00
Cursor Agent
3b2be37972
fix(campanha): B127 omnibox navigate + keyboard a11y from simplify
Use hook.navigate so draft search cannot clobber chips; replace cmdk with
ARIA combobox keyboard contract; memoize suggestion seeds per facet snapshot.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 20:30:55 +00:00
Cursor Agent
21e20a6719
feat(campanha): B127 omnibox filter bar chassis + Municípios pilot
Replace the stacked município filter controls with a shared omnibox
(chips + suggestions) backed by the frozen list URL contract; scenario
and non-default sort appear as presentation chips.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 20:30:55 +00:00
Cursor Agent
16c2358479
B129 — Restore anchored trend-control E2E regex after rebase 2026-08-02 20:07:35 +00:00
Cursor Agent
a92701db68
B129 — Format campaignMunicipalities E2E spec 2026-08-02 20:04:07 +00:00
Cursor Agent
4cb21576c2
B129 — Anchor trend-control E2E regex per codebase convention 2026-08-02 20:04:07 +00:00
Cursor Agent
5ded385ed9
B129 — Stabilize campaignPageChrome E2E helper for title + subtitle layout 2026-08-02 20:04:07 +00:00
Cursor Agent
5bb8bcc62b
B129 — Align municipality E2E with page chrome and scoped trend control 2026-08-02 20:04:07 +00:00
Cursor Agent
76597292c9
B129 — Simplify cleanup after overview removal
Fix duplicated JSDoc, trim stale int test, defer perf follow-ups in impl plan.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 20:03:58 +00:00
Cursor Agent
b4f2e2fa0a
B129 — Remove municipality list overview panel
Remove MunicipalityListOverview from /campanha/municipios and drop the
overview field/computation from loadMunicipalityListPageBundle. Filters,
scenario selector, and per-row goal coverage for sorting remain unchanged.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 20:03:58 +00:00
Francisco Solla
f176412e2b
Merge pull request #277 from fsolla/cursor/pool-i271-cbcf1835-00b9
B130 — Sidebar /campanha sem logo nem chip de escopo no topo
2026-08-02 17:03:28 -03:00
Cursor Agent
1cab8e9539
Harden B126 FAB e2e waits for layout chrome instead of page h1 2026-08-02 19:50:40 +00:00
Francisco Solla
7839a88a5a
Merge pull request #270 from fsolla/cursor/pool-i250-d89b6314-599f
B123 — Campanha — orientação na shell + aba (sem títulos grandes de seção)
2026-08-02 16:16:58 -03:00
Francisco Solla
5e7219f849
Merge pull request #269 from fsolla/cursor/pool-i260-ac255f26-42ec
B126 — FAB de ações rápidas (substituir bottom drawer persistente)
2026-08-02 16:15:01 -03:00
Cursor Agent
5fe0510f84
test(e2e): atualiza specs do drawer persistente para FAB overlay (B126) 2026-08-02 19:01:06 +00:00
Cursor Agent
6d793026d7
test(e2e): match page chrome title without exact text on desktop 2026-08-02 19:01:03 +00:00
Cursor Agent
3d13972e48
test(e2e): assert visible campaign page chrome on desktop viewport 2026-08-02 18:55:16 +00:00
Francisco Solla
62fb4c66e8
Merge pull request #263 from fsolla/cursor/pool-i259-b024c8a7-cbc1
OPS16 — CI: cooldown de deploy de produção 30 → 15 min
2026-08-02 15:46:51 -03:00
Cursor Agent
6ad0ed6cc7
style: format B126 quick-actions files 2026-08-02 18:40:07 +00:00
Cursor Agent
dbce18002c
B126 — FAB de ações rápidas substitui drawer persistente
Remove o bottom drawer não-modal (snap/peek/scroll-coupling) e introduz
um FAB em todas as larguras fora do Início e wizards. Abrir o FAB mostra
overlay modal: Dialog no desktop (busca em cima) e Drawer no mobile
(ações em cima), reutilizando strip e busca globais do Início.

Closes #260

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:39:27 +00:00
Cursor Agent
cad3556bce
OPS16: reduce production deploy cooldown from 30 to 15 minutes
Change DEPLOY_COOLDOWN_MS constant, update ci.yml deferral log, and
adjust unit test pins. Closes #259.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:38:36 +00:00
Francisco Solla
33d775396e
Merge pull request #258 from fsolla/cursor/pool-i253-48e749f5-b609
B124 — Sidebar Municípios: filtros salvos colados (sem disclosure)
2026-08-02 15:38:01 -03:00
Cursor Agent
9f19fb2f88
chore: format 2026-08-02 18:24:31 +00:00
Cursor Agent
57b1790e12
B124: corrige foco ao apagar filtro e remove useId morto
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:23:47 +00:00
Cursor Agent
1bcb49805c
format: HomeSearchMunicipalityGroup + homeSearchUi spec 2026-08-02 18:22:36 +00:00
Cursor Agent
7e6bc9d984
B125 — nearest município first in search suggest + wizard
- Restore B117 home search: scopeMunicipalities, merge prefix, session prompt
- Shared useNearestMunicipalitySlug (B14 key) for busca geral + wizard
- Wizard merge uses geoSlug only — no Perto de você / km copy on rows
- Impl plan: docs/plans/municipio-geo-topo-sugestoes-busca-impl.md

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:21:52 +00:00
Cursor Agent
9d86510b57
B124: filtros salvos sempre visíveis sob Municípios (sem disclosure)
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:20:27 +00:00
Cursor Agent
d4425251c6
style: prettier em arquivos B123 2026-08-02 18:11:54 +00:00
Cursor Agent
24e35d5a14
test: atualizar specs do shell para CampaignPageChromeProvider
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:11:11 +00:00
Cursor Agent
ccfbf592a8
B123 — orientação na shell + aba sem títulos de seção no corpo
Move page title/subtitle to CampaignMobileTopBar, CampaignDesktopHeader,
and tab metadata (Solla - Campanha - %s). Strip section headers and scope
chips from list/create routes; detail pages keep entity h1.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 18:08:47 +00:00
Cursor Agent
b50e799796
B122 — format 2026-08-02 17:58:24 +00:00
Cursor Agent
74c9c3a6e5
B122 — simplify: private button classes, drop bleed shell prop
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 17:57:40 +00:00
Cursor Agent
8a64e330e6
B122 — fix unit assertion for responsive overflow classes
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 17:55:13 +00:00
Cursor Agent
5b6af56b22
B122 — Início mobile: ações em grade 2×3 sem scroll
- CampaignHomeActionStrip: variant responsive (grid lt-md, strip md+)
- CampaignHomeActionButton: layout strip/grid/responsive class contracts
- CampaignHomeLayout: remove mobile bleed for grid gutter
- CampaignQuickActionsDrawer: explicit variant=strip
- Plans: inicio-acoes-grid-2x3-mobile + impl
- Tests: unit grid/strip + e2e mobile visibility

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 17:53:39 +00:00
Francisco Ignácio Fontoura Solla
9e4c537f8f Format campaignAccessRequestMemo int spec with Prettier. 2026-08-02 14:06:20 -03:00
Francisco Ignácio Fontoura Solla
3001b9da00 Pin access memo int test to the actor's campaign_user read.
Depth-1 populate of leftover leadership.user rows was counted as a second
fresh-actor select and falsely failed the per-request memo guard.
2026-08-02 14:05:53 -03:00
Francisco Ignácio Fontoura Solla
8bca9d8e82 Separate intention plans from engineering execution in agent skills.
plan-issue now shapes product intent only; agent-work-issue (pool) and
work-issue (human gate) deliberate the implementation plan before coding.
2026-08-02 14:02:18 -03:00
Francisco Solla
906008ef5d
Merge pull request #246 from fsolla/cursor/pool-i240-27201b52-9bc6
OPS13 — Remover pins de className / tokens Tailwind literais
2026-08-02 13:58:59 -03:00
Francisco Solla
1ecfd200be
Merge pull request #245 from fsolla/cursor/pool-i241-a6a48218-9e90
OPS14 — Form-action guard — política na escada, não no filename
2026-08-02 13:57:26 -03:00
Cursor Agent
62b6001ed8
OPS14 — form-action guard sweeps ladder, not filename
Replace the W4d filename-based convention spec with a sweep of every
src/app/(campaign) file that calls mapCampaignFormActionError without
runCampaignFormAction/Redirect. Migrate profile, leaderSupporter, and
convite-login to the shared wrappers; allowlist auth/password and the
existing bespoke formActions exceptions with documented reasons.

Closes #241

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 16:47:42 +00:00
Cursor Agent
0c605f4375
OPS13 — remove className/Tailwind literal pins from unit specs
Replace layout/spacing utility string assertions with stable contract
pins (data-slot, data-retracted, exported constant identity, ARIA).
Delete cosmetic-only padding/bleed/grid pins per plan remover-pins-classname.

Closes #240

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 16:47:27 +00:00
Cursor Agent
76ec34c436
OPS15: remove B102 TooltipProvider JSX nesting guard (#242)
Delete the source-shape regex pin from codebaseConventions.unit.spec.ts.
Behavioral coverage remains in campaignQuickActionsDrawer.unit.spec.tsx
(focus→suggest with priority:alta hits).

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-02 16:45:58 +00:00
Francisco Solla
8741eb5151
Merge pull request #154 from fsolla/cursor/pool-i149-d8165a59-5947
B110 — Wizard return path via allowlisted from query
2026-08-01 18:04:14 -03:00
Francisco Solla
ab910b499b
Merge pull request #153 from fsolla/cursor/pool-i148-3c37da6e-2281
B109 — Bottom drawer: dock polish, fullscreen search, exclude current entity
2026-08-01 18:03:27 -03:00
Cursor Agent
2e3740ef3f
Format B110 wizard return path changes 2026-08-01 20:40:04 +00:00
Cursor Agent
d5697bcfcb
B110 — wizard return path via allowlisted from query
Propagate return path from quick actions through the B98 wizard chain so
dismiss, skip, and chain end navigate back to the originating page instead
of always redirecting to Início.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 20:39:24 +00:00
Cursor Agent
fcd0206da6
chore: prettier format (B109) 2026-08-01 20:38:53 +00:00
Cursor Agent
490d5deeb7
B109 — bottom drawer: fullscreen search, wider labels, exclude current entity
- Add QUICK_ACTIONS_SNAP_FULL; drawer expands on search focus and hides action strip
- Tighten dock bottom gap; restore dock/collapsed on blur per scroll position (B100)
- Widen CampaignHomeActionButton to w-[5.5rem] for two-line labels
- Client-side filter excludes current page entity from drawer search hits
- Unit + e2e coverage for full snap, exclusion, and label readability
2026-08-01 20:38:10 +00:00
Cursor Agent
7a0f9ffed5
B111 — strip edge-to-edge bleed + silent flat delta on Início
- Allow horizontal bleed on home-actions retraction shell (overflow-y-hidden
  instead of overflow-hidden) so -mx-4 is not clipped by the chrome wrapper.
- Hide HomeSummaryDelta chip and period caption when delta is 0 or null;
  add shouldShowHomeSummaryDelta helper and unit pins.

Closes #150

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 20:36:19 +00:00
Francisco Solla
7317eb335a
Merge pull request #147 from fsolla/cursor/pool-i143-27c538e8-dc86
B108 — Wizard autofocus do 1º input + destaque do último cenário focado
2026-08-01 16:17:22 -03:00
Cursor Agent
19fb7ca76d
Format campaignWizardShell unit spec 2026-08-01 19:03:35 +00:00
Cursor Agent
ef4cef8c4c
Fix vote estimate compact highlight test assertion
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 19:01:12 +00:00
Francisco Solla
5cdcda98b9
Merge pull request #146 from fsolla/cursor/pool-i141-1ce99b5a-0b74
B106 — Início mobile — Voltar no header + Android back + skeleton das sugestões
2026-08-01 15:59:53 -03:00
Cursor Agent
a52a622ba5
B108 — wizard autofocus first input + active scenario highlight
- CampaignWizardShell: contentFocus prop skips title focus on form steps
- Wizard steps with inputs: contentFocus=none + autoFocus on first field
- VoteEstimateScenarioInputs compact: highlight follows activeScenario
- Municipality list compact overlay tracks focused scenario for highlight
- Unit tests for shell focus mode, compact classes, and votes step

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:59:42 +00:00
Francisco Solla
97ae71b0fe
Merge pull request #145 from fsolla/cursor/pool-i142-0f38ef32-58e4
B107 — Wizard município — sem título + busca sticky + skeleton
2026-08-01 15:57:38 -03:00
Cursor Agent
593b2273c2
style: prettier e2e wizard specs 2026-08-01 18:45:01 +00:00
Cursor Agent
ca18ce0f17
test(e2e): wizard município sem h1 visível (B107) 2026-08-01 18:44:39 +00:00
Cursor Agent
c524b1f19a
B105: format home search unit + mark plan implementado 2026-08-01 18:40:51 +00:00
Cursor Agent
98a69ab153
B105: bottom drawer handle top, scroll-up dock, discreet search peek
Revert handle to the top of the sheet; recalibrate collapsed/dock snaps
for handle+search and 2-line action labels; scroll ↓ collapses and ↑
re-docks; collapsed keeps a placeholder-less search peek that expands
on focus.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:40:51 +00:00
Cursor Agent
f80f672732
B106 — format 2026-08-01 18:38:50 +00:00
Cursor Agent
13a9ccf064
style: prettier CampaignWizardShell and tests 2026-08-01 18:36:55 +00:00
Cursor Agent
fc465639f2
B107 — wizard município sem título, busca sticky e skeleton
Remove the municipality step h1 via optional stepTitle on CampaignWizardShell.
Keep search input sticky under the wizard header, show hit-row skeletons
while suggest/search loads, and tighten mobile top padding when titleless.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:36:30 +00:00
Cursor Agent
84365ddf54
B106 — Voltar no header mobile, Android back e skeleton das sugestões
- CampaignHomeSearchChromeContext + publisher no Início
- CampaignMobileTopBar troca SidebarTrigger por Voltar quando uiFocused
- useHomeSearchFocusHistory com pushState/popstate para back do SO
- HomeSearchSuggestSkeleton enquanto isFetching sem hits
- Unit tests para history helper, top bar e skeleton

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:36:28 +00:00
Francisco Solla
de9377de85
Merge pull request #138 from fsolla/cursor/pool-i129-7bc105bb-7d33
B102 — Crash — busca no bottom drawer (focus / digitação)
2026-08-01 15:33:04 -03:00
Francisco Solla
1c2f767d96
Merge pull request #140 from fsolla/cursor/pool-i130-c5143bec-1fbc
B103 — Remover título Sugestões no empty state da busca
2026-08-01 15:24:21 -03:00
Cursor Agent
0174ae25db
B102 — pin layout TooltipProvider wrap + mark plan done
Conventions guard prevents regressing provider under CampaignAppScrollChrome.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:18:19 +00:00
Cursor Agent
653f04062b
B102 — fix drawer search crash on focus/type (TooltipProvider scope)
Lift TooltipProvider in the campaign app layout so it wraps
CampaignAppScrollChrome (scroll + quick-actions drawer). Focus→suggest
with priority-alta hits mounted CampaignHoverTooltip outside the old
provider and crashed the page. Unit pins cover focus→suggest and type→search.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:18:19 +00:00
Cursor Agent
259488fd33
B103 — omit Sugestões heading in home search suggest mode
Hide the visible h2 when resultKind is suggest; keep aria-label on the
section for screen readers. Search mode still shows Municípios heading.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 18:11:54 +00:00
Francisco Solla
313a041d5b
Merge pull request #137 from fsolla/cursor/pool-i131-803d9700-95db
B104 — Wizard: label genérica "Pular" nas ações encadeadas
2026-08-01 15:10:27 -03:00
Cursor Agent
2fa6e7298a
B104 — Wizard: label genérica "Pular" nas ações encadeadas
Substitui as quatro labels longas de skip (votos, tendência, sinal,
liderança) por uma constante compartilhada WIZARD_CHAIN_SKIP_LABEL.
Resolvers e condições de exibição permanecem intactos (B96/B98).

Closes #131

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 17:57:12 +00:00
Cursor Agent
1053c571eb
B101 — strip de ações: bleed sem gutter + hit area via padding
- CampaignHomeActionStrip: gap-0 no ul, px-4 interno (md:px-0)
- CampaignHomeActionButton: px-2 + box-content para hit area sem encolher coluna
- CampaignQuickActionsDrawer: -mx-4 bleed na strip (espelha Início)
- Pins unitários para classes de layout B101

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 17:55:54 +00:00
Cursor Agent
fded303fe6
fix(ci): stop trusting GET /v13 alias for production domain ownership
CI short-circuited "already aliased" when v13 listed pt.jorgesolla.com.br
on a fresh --prod deploy while the domain still pointed at an older
Current. Verify only via GET /v2/.../aliases and /v4/aliases?domain=.
Also drop invalid --yes on vercel alias set (CLI 55).
2026-08-01 16:12:30 +00:00
Cursor Agent
fdd318692c
fix(ci): clear domain gitBranch so CLI prod deploys can alias
Git-branch-bound domains only move via Git Integration. With Git
auto-deploy OFF, clear the binding, then promote + alias + verify.
2026-08-01 15:59:41 +00:00
Cursor Agent
bcca5da92c
fix(ci): verify domain alias via aliases API + vercel alias set
Promote + POST aliases still left pt.jorgesolla.com.br off the staged
deployment's GET /v13 alias list. Verify with GET /v2/.../aliases and
GET /v4/aliases?domain=, log steps live, and also run `vercel alias set`
(recommended when Git Integration is off).
2026-08-01 15:48:59 +00:00
Cursor Agent
c59b3fe31d
fix(ci): explicitly alias pt.jorgesolla.com.br after Vercel promote
Promote alone left the staged deployment on *.vercel.app only. Assign the
custom hostname via POST /v2/deployments/{id}/aliases after verifying it is
attached to the project domains list.
2026-08-01 15:37:16 +00:00
Cursor Agent
baa8f14f82
fix(ci): promote Vercel deploy to pt.jorgesolla.com.br
Disabling Git auto-deploy is correct for Actions-gated production. The
separate project toggle "Auto-assign Custom Production Domains" must stay
ON — when OFF, `vercel deploy --prod` only aliases *.vercel.app and leaves
the custom domain on an older Current (exactly what we saw after #122).

After each deploy, ensure-production-alias re-enables auto-assign if needed,
promotes the deployment, and verifies pt.jorgesolla.com.br is attached.
Also adds workflow_dispatch "Vercel promote production" for promote-without-rebuild.
2026-08-01 15:26:31 +00:00
Francisco Solla
f20fca0506
Merge pull request #120 from fsolla/cursor/pool-i106-d9499b1d-9029
B98 — Encadear ajustes após a ação principal do wizard
2026-08-01 09:06:11 -03:00
Cursor Agent
ca623e7aee
B98 — e2e espera a cadeia após save; knip limpa exports
Atualiza campaignHomeActions para navegar ao próximo elo
(votos→tendência, sinal→tendência, liderança→sinal) e remove
exports de skip de votos não usados.
2026-08-01 11:52:50 +00:00
Francisco Solla
a7394d3b9e
Merge pull request #119 from fsolla/cursor/pool-i114-36b6c1a5-2d0b
B100 — Bottom drawer mobile — dock visível + recolhe no scroll
2026-08-01 08:49:14 -03:00
Cursor Agent
d8f54a5d73
B98 — format Prettier nos arquivos do encadeamento 2026-08-01 11:42:08 +00:00
Cursor Agent
346949d035
B98 — Encadear ajustes do wizard após a ação principal
Após salvar (ou Pular) votos/tendência/sinal/liderança, navega ao
próximo elo da matriz v1 no mesmo município via entryAction, em vez
de voltar ao Início. Helper puro wizardActionChain + skips alinhados.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 11:41:26 +00:00
Francisco Solla
b809bf71dd
Merge pull request #118 from fsolla/cursor/pool-i116-2df83301-bbe5
OPS12 — CI bloqueia Closes/Fixes em PR só docs/plans
2026-08-01 08:39:47 -03:00
Cursor Agent
a056616d4a
Fix drawer snap geometry: handle at bottom, scroll on content ref
- Reorder drawer so handle sits at the screen bottom (snap shows bottom band)
- Hide dock content when collapsed instead of clipping through search
- Attach scroll-collapse listener directly on campaign-content-scroll ref
2026-08-01 11:35:09 +00:00
Cursor Agent
1ad8863994
OPS12 — CI bloqueia Closes/Fixes em PR só docs/plans
- Lógica pura em plansOnlyClosesGuard.mjs + CLI check-plans-only-pr-closes
- Job plans-only-closes em ci-pr.yml (rollup checks)
- Unit tests com fixture do incidente #115
- plan-issue, agent-pr-workflow, PR template e GUARDRAILS atualizados

Closes #116

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 11:26:19 +00:00
Cursor Agent
e09f01e683
Format + e2e manifest for Drawer/shell B100 2026-08-01 11:20:08 +00:00
Cursor Agent
1bb3fc7303
B100 — bottom drawer dock on load + collapse on scroll
- Replace expanded 55dvh snap with fixed DOCK height (12rem) + COLLAPSED handle
- Fix Base UI snap geometry via padding-bottom on Drawer popup
- Lift snap state to CampaignQuickActionsSnapProvider with pathname reset
- Collapse drawer when campaign-content-scroll passes threshold; reopen only via handle/pull
- Block collapse while global search is focused or active
- Dynamic content-scroll padding for dock vs collapsed peek
- Unit tests for dock default, scroll collapse, handle toggle; e2e on municipality detail

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 11:19:13 +00:00
Cursor Agent
5181116d84
B96 — Header wizard: X na tendência standalone, Pular só encadeada
resolveWizardTrendSkip(entryAction) espelha resolveWizardSignalSkip:
skip só quando entryAction presente e diferente de change-trend.
WizardTrendChoiceStep e WizardTrendNoteStep condicionam skip/trailingAction.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 11:18:13 +00:00
Francisco Solla
270c58424b
Merge pull request #113 from fsolla/cursor/pool-i107-0e9a1b5f-b248
B99 — Strip de ações do Início — edge-to-edge no mobile + gap menor
2026-08-01 08:14:36 -03:00
Francisco Solla
18bcee4454
Merge pull request #112 from fsolla/cursor/pool-i103-cc23cd9a-d9c9
B95 — Polimento busca município no wizard (bullets, gaps, título mobile)
2026-08-01 08:10:02 -03:00
Francisco Solla
36b39ffdd9
Merge pull request #110 from fsolla/cursor/pool-i105-88d1d9e1-68c5
B97 — Salvar tendência fecha o wizard (não volta ao começo)
2026-08-01 08:07:41 -03:00
Cursor Agent
cbddd8c796
OPS11 — cooldown de 30 min entre deploys de produção (Vercel)
- Script vercel-deploy-cooldown consulta último deploy READY via API Vercel
- Job deploy em ci.yml adia upload quando idade < 30 min (sucesso, não falha)
- Job requeue espera o residual e re-dispara ci.yml
- vercel deploy passa --archive=tgz para reduzir pressão de upload
- Testes unitários para computeCooldownWait e fetch da API

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 10:54:55 +00:00
Cursor Agent
5ce82e2229
B97 — Salvar tendência fecha wizard (redirect home quando status já aplicado)
Após salvar a nota, o RSC refresh via server action via
redirecionava à etapa de escolha. Agora  manda
para Início nesse caso; o client usa  no sucesso.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 10:54:51 +00:00
Cursor Agent
afa05fd5fc
B99 — strip de ações edge-to-edge no mobile + gap menor
- Bleed do slot home-actions com -mx-4 (compensa p-4 do scroll) só no mobile
- Gap horizontal da strip: gap-3 → gap-2
- Unit pin das classes de bleed em campaignHomeLayout

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 10:54:39 +00:00
Cursor Agent
7b3d3096cd
B95 — unit test for mobile wizard shell padding
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 10:53:33 +00:00
Cursor Agent
2fc73aa7a4
style: prettier B94 files 2026-08-01 07:44:00 +00:00
Cursor Agent
20c69bd0de
B94 — wizard empty state: município mais próximo (geo)
No idle do passo de busca de município, resolve client-side o município
mais próximo no escopo do ator quando a permissão de geo já está granted
(reuso B14: campaignGeolocation + municipalityProximity + malha lazy).

- Prop RSC accessibleMunicipalities na rota acoes/[slug]
- Hook useWizardNearestMunicipality (fail-soft, só inScope)
- Merge geo no topo da lista B92 via wizardMunicipalitySuggestMerge
- Unit tests do merge/format

Closes #94

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 07:43:42 +00:00
Cursor Agent
8a720b0285
style: prettier 2026-08-01 07:24:10 +00:00
Cursor Agent
0114c348d5
test: fix wizard votes step props and continuity scope filter spec
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 07:23:30 +00:00
Cursor Agent
dbf6a7fdce
B93 — wizard empty state continuity (visitados + última ação)
Merge client-side recent visits and last-acted municipality storage into
wizard municipality suggest idle list ahead of B92 server rankings.

- Add campaignLastActedMunicipality + wizardMunicipalitySuggestMerge libs
- Extend wizard-municipality-suggest with scopeMunicipalities for hit lookup
- Record last-acted on wizard write success (votes, signal, trend, leadership)
- Clear last-acted on logout alongside recent visits

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 07:21:56 +00:00
Francisco Solla
7f7c707d4d
Merge pull request #98 from fsolla/cursor/pool-i92-174dd43e-5e6f
B92 — Empty state do wizard: chassis + ranking de esquecidos
2026-08-01 04:17:53 -03:00
Francisco Solla
269fb79f37
Merge pull request #97 from fsolla/cursor/pool-i42-980ec2b6-2133
C11 — Escala planos (picker pós-C12 + paginação das demandas vinculadas)
2026-08-01 04:16:27 -03:00
Cursor Agent
2dcd3ab3f8
test(e2e): adapt demand form activity picker assertion (C11) 2026-08-01 07:02:06 +00:00
Cursor Agent
1ed65d0d81
B92 — wizard idle suggest: rank + loader + UI
Add wizard-municipality-suggest mode to home-search with cold-first
ranking (signal → E14 → trend → E10 class → 2022 votes). Wizard
municipality step now fetches suggestions when query is idle.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 07:01:19 +00:00
Cursor Agent
196e15b827
fix(agent-pool): pin fast=false on Cloud Agent spawns
Cursor's Create-Agent API defaults omitted `fast` to true, so pool
workers requesting composer-2.5 were billed as composer-2.5-fast.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:56:57 +00:00
Francisco Solla
bcfd3fe598
Merge pull request #90 from fsolla/cursor/pool-i12-d50dd384-d1b2
B57 — Delta 7 dias do total estimado no Início
2026-08-01 03:49:37 -03:00
Francisco Solla
d9a2414656
Merge pull request #84 from fsolla/cursor/pool-i26-856c30e7-8e05
B90 — Ações rápidas — Conceitos + Perfil (só busca staff)
2026-08-01 03:43:41 -03:00
Cursor Agent
2ac6f4c8fa
style: prettier (C11 touched files) 2026-08-01 06:39:34 +00:00
Cursor Agent
f394c76432
test(C11): fix unit spec imports and payload stub typing
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:38:55 +00:00
Cursor Agent
8372cec91d
C11: picker remoto de atividades na demanda + paginação de vínculos
- Substitui loadActivityOptions (pagination: false) por AsyncSearchCombobox
  com busca server-backed filtrada por município (searchActivityRelationOptions).
- Pagina card Demandas vinculadas no detalhe da atividade (10/página) com
  soma de custo em query separada e link Ver todas na lista filtrada.
- Adiciona filtro ?activity= na lista /campanha/demandas.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:37:53 +00:00
Cursor Agent
af6c180211
B57 — delta 7 dias do total estimado no Início
- Collection campaignVoteSummarySnapshot (snapshot diário statewide)
- Gravação idempotente on-read no loadCampaignHomeSummary + retenção 30d
- UI: seta + magnitude ao lado do total B56 (aria-label completo)
- Reusa rollup statewide para CG/candidato; assessor vê Δ global (opção A)

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:33:39 +00:00
Cursor Agent
a0a693fb27
style: Prettier tests after rebase 2026-08-01 06:29:16 +00:00
Cursor Agent
63a4b43951
style: Prettier (B90) 2026-08-01 06:28:15 +00:00
Cursor Agent
7844afcc6b
B90 — Ações rápidas Conceitos + Perfil (só busca staff)
Registry B79 para /campanha/conceitos e /campanha/perfil: catálogo vazio
para staff, busca global no drawer expandido via CampaignStaffGlobalSearch.
Leader em /perfil continua sem drawer (lockdown B89).

Closes #26

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:28:15 +00:00
Cursor Agent
ca80da178c
style: Prettier on B82 files 2026-08-01 06:24:54 +00:00
Cursor Agent
d5da7b0adf
B82 — Ações rápidas em lideranças (lista + detalhe)
Registra catálogo contextual no drawer B79 para /campanha/liderancas:
lista com update-leadership; detalhe com cinco wizards e prefill de
município quando N=1. Adiciona query leadershipId no wizard A4 e
CampaignQuickActionContextSync na ficha.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:24:54 +00:00
Francisco Solla
7261f3f588
Merge pull request #87 from fsolla/cursor/pool-i35-456a9692-831a
RS+ — Auth read leve + shells de senha
2026-08-01 03:23:51 -03:00
Francisco Solla
14c8f79ff9
Merge pull request #77 from fsolla/cursor/pool-i19-c4629a49-6cdb
B83 — Ações rápidas — Dobradinhas
2026-08-01 03:22:15 -03:00
Cursor Agent
21e5027b8b
B83 — catálogo de ações rápidas para /dobradinhas
Registra no drawer B79: lista (Nova dobradinha + 6 ações Início sem
prefill), detalhe e /nova (só Início). Integra com catálogos B80–B89.

Closes #19
2026-08-01 06:10:07 +00:00
Cursor Agent
104d6d4589
fix(RS+): depth:0 sem select parcial; form schema de convite separado
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:08:53 +00:00
Cursor Agent
7484aab28a
RS+ Fase 1+3: auth read leve, password fields e login state
- authenticateCampaignToken usa depth:0 + select mínimo (sem join media)
- getCampaignUserWithAvatar para layout/perfil; hasCampaignSession em rotas públicas
- CampaignPasswordFields reutilizado em reset, perfil e convite
- LoginForm migra para CampaignFormActionState + mapCampaignFormActionError
- invite login schema unifica refineMatchingPasswords

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:07:28 +00:00
Cursor Agent
6f61296064
fix: close first it block in onda0Provision int spec
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:04:05 +00:00
Cursor Agent
cbe171c597
O0+ (#34): consent key DRY, SQL int tests, Footer condicional, B68 suggest RSC
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 06:04:05 +00:00
Francisco Solla
5c6ed8cf31
Merge pull request #82 from fsolla/cursor/pool-i23-0fce1b59-4cb2
B87 — Ações rápidas — Assessores (unrestricted)
2026-08-01 03:03:49 -03:00
Cursor Agent
86f948540b
fix: rebase on main (B85 demands + B87 assessores) 2026-08-01 05:49:25 +00:00
Cursor Agent
be317191b6
B87 — Ações rápidas assessores (unrestricted)
Registry B79 para /campanha/assessores (lista + detalhe): catálogo
new-advisor com deep link ?criar=1; mount gate só unrestricted;
/novo redireciona para o deep link; busca global permanece na lista
(B91 no drawer).

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:48:08 +00:00
Cursor Agent
c73034aa77
B91 — Mount drawer search only when expanded; unify body export
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:46:42 +00:00
Cursor Agent
5ec167f8dc
B91 — Fix drawer search unit test assertions
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:44:39 +00:00
Cursor Agent
d639988b83
B91 — Global search in mobile quick-actions drawer
Extract CampaignGlobalSearchMount (provider + body) from home chrome
so the same home-search contract mounts in CampaignQuickActionsDrawer
when expanded. Refactor CampaignHomeStaffChrome to use the provider.

Closes #55

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:42:59 +00:00
Francisco Solla
90d130af33
Merge pull request #80 from fsolla/cursor/pool-i21-97442a76-e99b
B85 — Ações rápidas — Demandas
2026-08-01 02:41:05 -03:00
Cursor Agent
dfa080f74d
B89 — Ações rápidas em Contatos (liderança)
Wire the B79 drawer on /campanha/contatos for leader role with the B45
catalog (register-supporter, my-contacts). Actions use in-page hash anchors
instead of staff home-search; no drawer on Início (existing B79 gate).

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:27:45 +00:00
Cursor Agent
aa0cc4e15a
test: resolve searchStaffMunicipalityHits conflict after rebase 2026-08-01 05:27:41 +00:00
Cursor Agent
2baad93f71
test: retry municipality pair when word-start queries collide
Allocator stays slug-free (miss #73); skips Conde/Condeúba-style prefixes.
2026-08-01 05:26:12 +00:00
Cursor Agent
7812dee1f3
style: Prettier (B85) 2026-08-01 05:26:12 +00:00
Cursor Agent
c05b04236e
B85 — Ações rápidas no drawer de Demandas (lista + detalhe)
Registra catálogo contextual no chassis B79:
- Lista: um launcher register-demand → /demandas/nova (wizard A5 ainda stub)
- Detalhe: A1–A5 com prefill de município via context sync
- Expõe municipalityId no view model para prefill do form de criação

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:26:12 +00:00
Cursor Agent
a7ecdbc844
chore: format B88 quick-action files 2026-08-01 05:12:11 +00:00
Cursor Agent
9b76f19b85
B88 — Ações rápidas em Organizações (lista + detalhe)
Registra /campanha/organizacoes no registry B79 com launcher
new-organization na lista; detalhe vazio até existir filtro de
lideranças por org ou município óbvio único.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 05:11:31 +00:00
Cursor Agent
bdbab71112
fix(test): retry municipality pair when word-start queries collide 2026-08-01 04:52:17 +00:00
Cursor Agent
2cd1e7cfd7
fix(test): retry municipality allocation until word-start queries are distinct 2026-08-01 04:49:36 +00:00
Cursor Agent
b7ad9ad326
merge: sync with main (B80 municipalities quick actions) 2026-08-01 04:44:03 +00:00
Cursor Agent
39b9065ad2
style: Prettier after rebase merge 2026-08-01 04:35:38 +00:00
Cursor Agent
66e84ab85c
fix: resolve rebase conflict markers in quick-action unit spec 2026-08-01 04:35:38 +00:00
Cursor Agent
eea2d9afa1
B84 — Ações rápidas na vertical Atividades (lista + detalhe)
Registra catálogo B84 no registry B79: Nova atividade e Planejar giro na
lista; wizards A1–A5 com ?municipio= no detalhe quando há município, mais
atalhos Editar/Tarefas/Atualizações. Context bridge no detalhe; drawer
omitido em /atividades/giros (compositor E13 já tem CTAs).

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 04:35:38 +00:00
Cursor Agent
3463389942
style: prettier registry 2026-08-01 04:22:25 +00:00
Cursor Agent
6f4402dab6
style: prettier 2026-08-01 04:21:25 +00:00
Cursor Agent
6030d42e2f
B80 — Ações rápidas em Municípios (lista + detalhe A1–A5)
Registra catálogo staff no drawer B79 para /campanha/municipios e
/campanha/municipios/[slug]: lista espelha Início (sem prefill); detalhe
prefill ?municipio= nos wizards A1–A5 e omite Ver esquecidos.

Closes #16

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 04:21:15 +00:00
Cursor Agent
09e76ec8cb
B81 — format 2026-08-01 04:10:16 +00:00
Cursor Agent
7f2ac29368
B81 — simplify: path constant, role cache, href parity test
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 04:09:35 +00:00
Cursor Agent
8c9eeaf273
B81 — register staff Início quick actions on /campanha/territorios
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-08-01 04:06:22 +00:00
Cursor Agent
18fe4f8e9b
fix(agent-pool): ban -fast model slugs
Never map or request fast=true. Slugs ending in -fast are stripped to
the base (with a warn); model-selection and the pool plan document the ban.
2026-08-01 03:34:35 +00:00
Cursor Agent
629364f3e4
fix(agent-pool): map Grok effort + Kimi reasoning slugs to Cloud API
cursor-grok-4.5-{low,medium,high}[-fast] and kimi-k3-low were falling
back to composer-2.5 against live /v1/models (grok-4.5 + effort,
kimi-k3 + reasoning). Teach resolvePoolModel the mapping, document
when to pick each Grok effort in model-selection, and align plan/work
skills.
2026-08-01 03:32:41 +00:00
Francisco Ignácio Fontoura Solla
6c90d76f10 test: pin exported skip predicates so knip stays clean 2026-07-31 21:09:20 -03:00
Francisco Ignácio Fontoura Solla
f021992de0 ci: main-only cutover with gated Vercel deploy
Remove stage/promote/Neon smoke. PR CI targets main; ci.yml full-suite
verifier deploys via vercel CLI and requeues when HEAD moves. Pool uses
POOL_GITHUB_TOKEN and POOL_BASE_REF=main. Docs/skills/rules retargeted.
2026-07-31 20:57:18 -03:00
Francisco Ignácio Fontoura Solla
42d1a7be93 ci(pr): prettier + ignore local tool artifacts in format check 2026-07-31 20:57:18 -03:00
Francisco Ignácio Fontoura Solla
9d810d1564 ci(pr): cascade phase1→phase2 with blast-radius skips
Add scope classifiers for code/build jobs, always-on test-location
guardrails, and gate:ci parity. Default affected base is origin/main.
2026-07-31 20:57:18 -03:00
Cursor Agent
7a876ec334 refactor(notifications): bell loaders read through owner access, no bypass
countUnreadNotifications/loadNotificationList took a caller-supplied
recipientID with module-level admin bypass — one misuse away from reading
someone else's inbox. They now take the actor (CampaignUser) and read
with overrideAccess: false under canReadOwnNotifications, keeping the
explicit recipient filter as a second fence. Pin: owner isolation at the
loader level in campaignNotifications.int.spec.ts.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-31 20:57:18 -03:00
Cursor Agent
6705db80eb security: lock public globals update to payloadAdminOnly (Pass 4 P0)
The four public globals (site-settings, home, metadata, privacy-policy)
spelled update access as "any authenticated user" — and campaign JWTs
authenticate against /api/*, so any campaign session (leader included)
could PATCH the public site. payloadAdminOnly matches the collection
lockdown precedent; editors lose config-global writes deliberately
(content editing stays on canManagePublishedContent collections).

Pins: identity + deny (coordinator/editor) + allow (admin fn) + anonymous
read in collectionAccessLockdown.int.spec.ts. Recurrence guard:
globalAccessConventions.unit.spec.ts bans the Boolean(user) shape in
src/globals.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-31 20:57:18 -03:00
Cursor Agent
84fed02ee5 test(miss-54): deterministic guard for unsettled goto→goto + nav convention
The SavedFilters incident shape — a page.goto fired while the previous
heavy RSC navigation was still in flight (net::ERR_ABORTED) — gets a
build-level guard: two gotos with no settle between them (await expect /
waitForURL / click / reload / login / catch) fail the suite. Measured
baseline on the current tree: 58 gotos across 17 specs, 0 unsettled
pairs — the six SOFT pairs from the miss audit all settle on awaited
assertions, so the guard starts clean without churning them.

TESTING.md records the convention the miss asked for: goto for cold
loads and URL contracts; in-shell navigation for continuity scenarios;
interactive-state waits before dynamic clicks; a dev-mode caveat for the
campaignLeaderships chip spec (deterministic dev failure at the
suggestion/commit step, prod green — ledgered).
2026-07-31 20:57:18 -03:00
Cursor Agent
5c59793e4c test(miss-53): gate WebAuthn ceremonies on authenticator readiness
The app's biometrics probe is one-shot per island mount: when
isUserVerifyingPlatformAuthenticatorAvailable() resolves before the CDP
virtual authenticator answers, the enrollment form / biometric button
never mount and no wait flips them (prod-build CI flake). New
expectCampaignBiometricsReady(page) polls the platform answer BEFORE the
surface whose island probes it; adopted at both ceremony entries in
campaignWebAuthn (verified green locally). Conventions pin:
e2eNavigationConventions fails the build when a virtual-authenticator
spec skips the readiness gate.
2026-07-31 20:57:18 -03:00
Francisco Ignácio Fontoura Solla
b1a34a8c3a fix(tests): allocate homeSearch municipalities; scope B42 trend drawer 2026-07-31 20:57:17 -03:00
Cursor Agent
e51a1dc9b2 style: prettier campaignHomeActions after URL anchor edits 2026-07-31 20:57:17 -03:00
Cursor Agent
03314e620a test(miss-73): deterministic allocator guardrails in conventions spec
Recurrence prevention for the deadlock class, three rules:
1. a spec file calling getMunicipality(<arg>) while mutating
   municipalities (assign/touch/create*/payload.update municipality)
   fails the build — the class miss #73 named;
2. hardcoded municipio=<slug> deep links banned in tests/e2e —
   claimMunicipality().slug is the only source;
3. e2e name: /.../ regex literals containing a catalog municipality name
   must be ^-anchored — the literal-regex dodge of the P3-C template
   guard, keyed on the catalog so copy regexes stay untouched.
2026-07-31 20:57:17 -03:00
Cursor Agent
6b894a04c5 test(miss-73): allocate municipalities, never pin seeded slugs (deadlock class)
Int specs pinning seeded rows (cairu/feira-de-santana/catalog[0..2] or
class-derived fixed slugs) and mutating them deadlocked municipality_rels
under the parallel suite (40P01); e2e specs deep-linking ?municipio=cairu
raced the same row from two workers.

- homeSearchStateDeputies: 2 fixed slugs -> 2 allocations.
- municipalityPageData classe test: rewritten class-AGNOSTIC — allocates
  rows until two distinct territorial classes (same pinned behavior:
  filter narrows, totals honest, classe sort = descending weight), no
  fixed reduto/marginal slugs. nivel test + facet test: plain allocations.
- campaignHomeActions/campaignWizardChrome: 11 hardcoded cairu deep links
  -> claimMunicipality(); hit clicks disambiguate by href (catalog names
  are not prefix-unique); caption/URL assertions use ^-anchored full-URL
  regexes (P3-C template guard). /Cairu/i literals gone.

Verified: int 17/17, e2e 18/18 (dev mode), unit 1106/1106.
2026-07-31 20:57:17 -03:00
Francisco Solla
7a66fe4f25
Merge pull request #5 from fsolla/cursor/p4-shared-sheet-host-78a7
Miss #52 guardrail: shared sheet host on the chip-relation lists + fail-loud guard
2026-07-31 13:21:48 -03:00
Cursor Agent
c83cf28497 test(miss-52): sheet cells without the shared host fail loud
- CampaignCellEditOverlay: dev-mode console.error when a variant=sheet
  cell mounts without CampaignListSheetProvider (prod keeps the
  lazy-mount fallback for resilience; unit tests exercise it, so the
  invariant is development-only).
- sharedSheetHostConventions.unit.spec.ts: any tsx rendering
  <MunicipalityPortfolioCell / <LeadershipStateDeputyRelationCell must
  reference CampaignListSheetProvider — the next chip table cannot
  reintroduce per-cell Drawers by omission.
2026-07-31 13:17:19 -03:00
Cursor Agent
9ae062c77c refactor(lib): close the lib→utilities type-import hole + Local API omission guard
- suggestionCatalog type-imports MunicipalityTerritorialClass from
  lib/territorialClassAnchors (the single source) instead of the
  utilities re-export — direction inversion introduced in Pass 3.
- ESLint src/lib zone: @/utilities|components|app restriction now rejects
  type imports too (allowTypeImports: false); shared types live in lib
  contract modules by policy.
- engagementLevel imports DAY_MS from lib/text (fold missed in P3-K).
- searchHomeAdvisors: delete stale 'Intentional admin bypass' comment —
  the call is overrideAccess: false under the staff gate.
- New guard localApiOverrideAccessConventions.unit.spec.ts: payload calls
  with user must declare overrideAccess (baseline measured: 59 calls, 0
  missing), and runtime dynamic import/require of payload/next server
  modules is banned in utilities (dodges the server-only sweep).
2026-07-31 13:17:10 -03:00
Cursor Agent
0991c04cd7 fix(seed-posts): import dieWithLabel before calling it (Pass 4 P1)
The P3-H CLI consolidation left seed-posts calling dieWithLabel with no
import — a ReferenceError on the empty-fetch abort path, invisible to
lint/typecheck/knip. Recurrence guard in the same delivery:
scriptCliConventions.unit.spec.ts fails the build when a script calls
dieWithLabel/dieAgent without importing (or defining) it, and bans
re-spelled 'const DAY_MS =' outside src/lib/text.ts (P3-K's fold leaked
one private copy in engagementLevel.ts).
2026-07-31 13:17:10 -03:00
Francisco Solla
8dd60cf962 A6 — Dobradinha 2026 automática quando o TSE publicar candidaturas (pós-15/08) (#82)
* docs(A6): freshness audit da #27 no plano de dobradinhas 2026

electionInsights.ts foi deletado no Pass 2 W4a (modulo puro novo),
electionAlliances.ts nao nasce (campoParties + electionPartySpectrum ja
carregam a taxonomia), questoes em aberto resolvidas (tiers, score,
proporcionais, lockdown de lideranca), card na aba Eleicoes, overview da
lista fora de escopo, cache election-tse com bust na conciliacao Fase 5.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* feat(A6): insight de dobradinhas 2026 na aba Eleicoes do municipio

Lista ranqueada de candidatos proporcionais que concorrem de novo em 2026
(runningAgain2026=sim) por geografia do municipio: score 0,6 alinhamento
(campo 2026 + espectro Bolognesi) + 0,4 forca local normalizada pelo maximo
do conjunto. Estado pendente ate a conciliacao pos-15/08; Solla excluido
(referencia, nao parceiro); leitura derivada sem escrita; loader cacheado
sob a tag election-tse como os irmaos; link para /campanha/dobradinhas
como caminho de registro operacional.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* test(A6): cobertura unit/int do insight + lease das colecoes eleitorais

Unit: tiers (campo/espectro/falha-para-o-meio), score, ordenacao, empates,
limite, zero votos. Int: pending2026 sem conciliacao (mesmo com votos
locais), so adversarios, misto com exclusao de Solla/nao e geografia
alheia. Lease advisory election-collections serializa os dois specs que
tratam as colecoes eleitorais como test-owned (o import wipe-all apagaria
as fixtures do novo spec no meio do assert, 48-way paralelo).

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* refactor(A6): fixes do /simplify (3 reviewers paralelos)

P1: campo coalition morto removido do input/select/mapping (sem consumidor).
P1: hooks do lease com timeout explicito de 120s nos dois specs eleitorais
(o wait advisory passa dos 10s default do Vitest sob contencao — flake em
ambas as direcoes). P2: sonda de conciliacao via payload.count (idioma da
casa), desempate final por candidateNumber (ordem deterministica), geography
como prop na secao (sem lookup duplicado), casing Dep. Federal/Estadual
alinhado, marcador 1313 via BASELINE_TICKET_2022, entrada no CHANGELOG.

Declinados com justificativa: zones como tupla nao-vazia (churn em tipo
compartilhado por assert provada no catalogo) e early-start da promise
antes do Promise.all (loader-dentro-do-Suspense e o idioma da casa; ganho
so em cache frio).

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-31 00:14:27 -03:00
Cursor Agent
83f0b1cf48 fix: remove leftover merge conflict markers in int test 2026-07-31 02:34:15 +00:00
Cursor Agent
c7aac4da1b test: pin home search municipality scope fixtures to distinct slugs 2026-07-31 02:34:15 +00:00
Cursor Agent
5d45909780 fix: drop unused quick-action setter hook until B80 2026-07-31 02:34:14 +00:00
Cursor Agent
224cbf84f5 test: cover quick action context setter hook for knip 2026-07-31 02:34:14 +00:00
Cursor Agent
a2eabaccba B79 — chassis do bottom drawer de ações rápidas + busca global (mobile)
Monta CampaignQuickActionsDrawer não-modal no layout (app) para staff fora
do Início e de /acoes, com snap collapsed/expanded, padding de peek no scroll,
registry vazio para catálogos B80+, e busca global reutilizada via
CampaignStaffGlobalSearch.

Closes #15

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-31 02:34:08 +00:00
Francisco Solla
b2b11c3dfe Merge pull request #81 from fsolla/cursor/d2-push-sino-in-app-af30
D2 — Push + sino in-app
2026-07-30 23:25:11 -03:00
Cursor Agent
b9a81f7ad0 D2 — skip notification hooks in Vitest; fixture cleanup for push rows 2026-07-31 02:09:52 +00:00
Cursor Agent
de67acf41b D2 — Push + sino in-app para /campanha
- Collections Notification e PushSubscription com migration
- Sino no header (mobile + desktop) com drawer de avisos lidos/não lidos
- Hooks em municipalityUpdate, supporter, activity e convite aceito
- Push Web via web-push + handlers no service worker /campanha/sw.js
- Opt-in LGPD em /campanha/perfil (chave campanha-notificacoes-push)
- Testes unit/int de contrato e access

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-31 01:51:41 +00:00
Francisco Ignácio Fontoura Solla
773e4426eb fix(test): queries distintas em specs de portfolio de município
Nova Soure vs Nova Canaã compartilham o primeiro token; distinctWordStartQuery
escolhe palavra que casa só com o alvo e evita falso positivo no assessor.
2026-07-30 22:29:20 -03:00
Francisco Ignácio Fontoura Solla
0fece8cef5 fix(test): isola municípios nos homeSearch specs para evitar deadlock
Specs de portfolio deixam de mutar cairu/feira compartilhados e passam a
usar getMunicipality() alocado; testDatabaseLease ignora updatedAt volátil.
2026-07-30 22:07:40 -03:00
Francisco Ignácio Fontoura Solla
b533139eb2 feat(ops): promote último commit ci-stage green sem exigir head verde
agent:promote passa a escolher o SHA mais novo à frente de main com
ci-stage success e usa promote/last-green quando o head de stage está
vermelho, destravando o bootstrap manual para main.
2026-07-30 21:56:50 -03:00
Cursor Agent
90a97d3c76 style: prettier 2026-07-31 00:27:50 +00:00
Cursor Agent
6c8d7d418c B78 — Indicar carregamento na navegação do wizard
Reusa CampaignListPendingBoundary no layout autenticado para compartilhar
useTransition entre o header mobile e o conteúdo do passo. Voltar, X, Pular,
seleção de município e tiles de passo passam por CampaignWizardNavLink;
o corpo dima com aria-busy e o header mostra skeleton nos slots de título.

CampaignTransitionAnchor agora retorna a promise do router para manter o
pending honesto até a navegação terminar.

Closes #14
2026-07-31 00:24:39 +00:00
Francisco Solla
e04db48742 feat(ops): auto-promote stage→main e merge em stage sem exigir head (#68)
* feat(ops): auto-promote stage→main e merge em stage sem exigir head

Substitui o promote manual por workflow disparado no ci-stage green,
relaxa strict na proteção de stage (script configure:branch-protection)
e documenta o novo paradigma em AGENT-OPS e work-issue.

* style: format AGENT-OPS and configure-branch-protection for ci-pr

* feat(ops): pnpm push com ensure-repo-deps determinístico

Bootstrap install + Husky antes de todo push (pnpm push e pre-push).
Documenta fluxo canônico em work-issue, agent-pr-workflow e AGENT-OPS.

* fix(ops): pnpm push roda gate no script, sem depender do Husky

Cloud environment.json chama prepare explicitamente após install.

* test(int): isolate advisor portfolio search from shared cairu slug

Parallel int specs were stomping municipality.advisors on the fixed
cairu row; allocate per-test municipalities instead.
2026-07-30 21:12:52 -03:00
Francisco Solla
5ee9e840a2 Merge pull request #67 from fsolla/cursor/wizard-mudar-tendencia-a971
B64 — Wizard mudar tendência (grid colorido + justificativa + Pular)
2026-07-30 20:10:37 -03:00
Francisco Solla
680ff56754 feat(agent-pool): orquestrador de pool de workers Cursor Cloud (fases A–D) (#65)
* feat(agent-pool): fundacao read-only do orquestrador (fase A)

- docs/plans/agent-pool-orchestrator.md: plano completo (14 secoes)
- buildClaimQueue extraido de agent-claim para agent-github (comportamento identico, pinado)
- agent-pool-eligibility: isAutonomousClaimable puro e fail-closed + buildPoolQueue
- agent-pool-state: parsePoolConfig, marcadores pool-worker, classifyPoolClaim, spawn plan, auto-stop
- agent-pool-github: leitura de repo variables (degrada nao-estrito), claims/PRs/migration-lock
- pnpm agent:pool -- status|tick --dry-run (inspecao local; live chega na fase B)
- 34 specs unit novos (992/992 verde); lint/tsc/knip/cycles/format verdes

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* feat(agent-pool): spawn remoto de workers via Cursor API (fase B)

- agent-pool-cursor: client fino da Cloud Agents API v1 (fetch injetavel; CursorApiError com status/body)
- agent-pool-models: resolvePoolModel contra /v1/models (id/alias, sufixo -fast -> param, fallback composer-2.5 com warn)
- agent-pool-prompt: contrato do worker (claim pre-feito, pula passo 1; PR base stage + Closes #N; promote proibido)
- claim coordenado no tick: lock otimista + flip + marcador; agentId idempotente bc-<uuid>; 409 recupera; rollback em falha de spawn
- agent:pool workflow --action start|stop|pause|resume|tick|status (variables POOL_*); doctor; wrappers gh workflow run
- .github/workflows/agent-pool.yml (dispatch-only; schedule/triggers na fase C)
- 19 specs novos (1011/1011); lint/tsc/knip/cycles/format verdes

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* feat(agent-pool): ciclo de vida do supervisor (fase C)

- derivePoolClaims com status real dos runs (getCursorRun), heal de spawn via agentId deterministico, cancel de duplicatas
- reconcilePoolClaims: falha terminal -> comentario + blocked + archive; merge concluido -> slot livre + archive
- auto-stop real: fila drenada + zero ativos -> POOL_ENABLED=false (exclusoes migration-busy nao desligam)
- job summary markdown (GITHUB_STEP_SUMMARY) — superficie remota de status
- workflow: schedule */10min + pull_request closed em stage (ativam na default branch apos promote)
- 2 specs novos (1013/1013); lint/tsc/knip/cycles/format verdes

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* docs(agent-pool): runbook operacional e adendo do spike (fase D)

- skill agent-pool: runbook humano (pre-requisitos, start/stop/pause remoto, ciclo de vida, triage, smoke de aceite)
- AGENT-OPS: linha na tabela de comandos + secao Agent pool (canal, secrets, ativacao pos-promote)
- CHANGELOG-AGENTS: entrada curta da entrega
- engineering-audit: pause/resume do pool no protocolo do modo solitario
- plano secao 14.1: evidencias do spike 2026-07-30 (environment repo-file confirmado, id bc-<uuid>, degradacao 403, pendencias do primeiro run com CURSOR_API_KEY)

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

* fix(test): teardown flake no homeSearchUi.unit.spec (dono do CI)

O scheduler do react (prefetch do next/link) podia disparar apos o teardown
do jsdom e derrubar o job unit com 'window is not defined' (run 30587862940).
afterEach(cleanup) — padrao dos 14 specs irmaos.

Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
2026-07-30 20:08:34 -03:00
Cursor Agent
fcbc463990 fix: format + wire WIZARD_TREND_SAVED_MESSAGE for knip 2026-07-30 23:03:25 +00:00
Cursor Agent
f0f3d4534d B64 — Wizard mudar tendência (grid + justificativa + skip) 2026-07-30 23:03:25 +00:00
Cursor Agent
8ff9e20a88 fix(B55): defer share strategy to click + stabilize unit tests 2026-07-30 22:47:03 +00:00
Cursor Agent
b96bf190e8 B55 — Busca global: ações de linha (WA e compartilhar)
Adiciona trailing actions nas linhas de resultado da busca global:
- WhatsApp em lideranças e assessores (reusa whatsAppHrefForPhone)
- Compartilhar em demandas e atividades (Web Share nativo com fallback wa.me)
- trailingAction fora do Link da linha para não roubar navegação

Closes #9
2026-07-30 22:47:03 +00:00
Cursor Agent
10377d90fb feat(campanha): remove mobile bottom nav (B73)
Remove CampaignBottomNav and getCampaignBottomNav — mobile navigation
is now sidebar-only (Sheet via SidebarTrigger). Drop the pb-24 padding
that reserved space for the fixed bar; scroll uses symmetric p-4 on mobile.

Impeccable: N/A (shell chrome removal, class B per plan).

Plan: docs/plans/remover-bottom-nav-mobile.md
2026-07-30 22:13:07 +00:00
Francisco Solla
142b0963e0 B54 — Busca global: layout responsivo dos resultados (#60)
* feat(campanha): responsive grid and viewport cap for home search results (B54)

Adds HomeSearchResultsLayout with 1/2/3-column breakpoints and allocateHitBudget so Municípios keep priority while total hits stay within per-viewport limits.

Closes #8

* fix(campanha): un-export knip-unused home search budget helpers
2026-07-30 18:45:44 -03:00
Francisco Ignácio Fontoura Solla
30170509f7 fix(e2e): WebAuthn on prod build, saved-filters navigation, Vercel scripts
CI e2e serves a production build on localhost; Next inlines NODE_ENV so
WebAuthn RP resolution now treats localhost requests as test mode. Saved
filters avoids a racing bare goto. Analytics/SpeedInsights render only on Vercel.
2026-07-30 17:37:20 -03:00
Francisco Ignácio Fontoura Solla
6a20e98047 fix(campanha): share one Drawer across municipalities mobile cards
Root cause of the prod-build React #130 on /campanha/municipios: the
md:hidden mobile-cards tree still hydrated ~25 rows x ~5 sheet controls
(~125 eager Drawer/DrawerCloseButton instances) even on wide viewports.
Filtered ?q=... routes failed more often only because they exercise the
list with filters active — the search param itself was not the bug.

CampaignListSheetProvider hosts a single bottom Drawer for the whole
mobile section; CampaignCellEditOverlay sheet cells portal their
body/footer into it while keeping per-cell open state and autosave
scaffolds. Footers with DrawerCloseButton are only created once a sheet
actually opens. Desktop popover path is unchanged; per-cell Drawer
remains the fallback for surfaces without the provider.

Also: DrawerCloseButton (function render prop, avoids base-ui element
render in prod bundles) replaces the DrawerClose render={<Button/>}
pattern across call sites; stale e2e selectors refreshed; e2e affected
script + playwright github/list reporters for CI; use-mobile reverted
(no consumers of the gate remain).

Guardrail filed as issue #52.
2026-07-30 17:37:20 -03:00
Francisco Ignácio Fontoura Solla
2e0cc2c03e chore(ci): parallel PR jobs, affected tests, push gate, owner-of-red-CI policy
- ci-pr.yml: split the serial checks job into parallel lint/format/
  typecheck/knip/cycles + unit/int (vitest --changed vs PR base via
  scripts/test-affected.mjs, full fallback on high-risk paths) + e2e
  (manifest path->spec via scripts/e2e-affected.mjs, blocking) + build;
  migration-lock gains checkout + pull-requests:read (fixes the failures
  that blocked PR #50)
- ci.yml (main): full safety net — seed:minimal + int full + build + e2e
- package.json: gate:fast / gate:push; .husky/pre-push runs gate:push
- docs: "Dono do PR, dono do CI" policy in AGENT-OPS + work-issue +
  engineering-standards; changelog entry; plans for OPS3-OPS7
2026-07-30 17:37:20 -03:00
Francisco Ignácio Fontoura Solla
5b4ab9c020 fix(ci): unblock PR checks — checkout in migration-lock, seed-proof leadership spec
migration-lock ran `gh pr list --json files` without actions/checkout, so
every PR failed the job in 3s with "not a git repository". The leadership
portfolio spec asserted the exact global hit set with fixed names, which the
minimal seed (Seed Liderança Um/Dois) collides with via word-start 'Lider';
switch to the unique-token pattern already used by the demands spec.
2026-07-30 13:25:49 -03:00
Francisco Ignácio Fontoura Solla
9eafe6081c feat(campanha): add Demandas group to home global search (B53)
Staff can find demands by title from the Início omnibox with municipality and status in the secondary line, scoped by campaignDemand access.
2026-07-30 12:39:35 -03:00
Francisco Ignácio Fontoura Solla
40e7ecea33 test: fix pre-existing unit drift — wrap Tooltip-needing renders in TooltipProvider (944/944 green) 2026-07-30 05:35:25 -03:00
Francisco Ignácio Fontoura Solla
d0249e5ee5 fix: drop unused ts-expect-error in manifest pin 2026-07-30 04:46:51 -03:00
Francisco Ignácio Fontoura Solla
777fde2eed ci: PR gate with minimal DB + build + migration-lock; stage gate without build 2026-07-30 04:44:37 -03:00
Francisco Ignácio Fontoura Solla
6ac26404d2 feat: db:seed:minimal — minimal synthetic campaign DB for agents/CI 2026-07-30 04:43:01 -03:00
Francisco Ignácio Fontoura Solla
3f16998716 chore: format B63 wizard signal files. 2026-07-30 04:03:11 -03:00
Francisco Ignácio Fontoura Solla
b4ce8e45d0 docs: align B63 entry query param with B70 and record simplify deferrals. 2026-07-30 04:02:30 -03:00
Francisco Ignácio Fontoura Solla
8c7633da4a B63+: wire wizard signal steps to B75 chrome and DRY skip. 2026-07-30 04:01:49 -03:00
Francisco Ignácio Fontoura Solla
09fa21d4b3 B63: wizard registrar sinal (grid de tipos + passo de texto).
Two-step UX-1 flow on the B59 chassis with URL contract ?signalType= and optional ?entryAction= for embedded skip; reuses createMunicipalityListSignalFormAction.
2026-07-30 04:01:48 -03:00
Francisco Ignácio Fontoura Solla
dbd9d0fa3e feat(campanha): ship B70 leadership update wizard (grid + short form).
Replaces the post-municipality stub with a full wizard: tile grid, contact+leadership writes, entryAction skip, and Continuar after save.
2026-07-30 03:51:25 -03:00
Francisco Ignácio Fontoura Solla
625f25c984 B77+: simplify vote wizard helpers after single-screen delivery.
Unify violation highlights with getWizardVoteViolation, share the expected-votes save error constant, and drop dead parseWizardVoteDraft plus unused municipalitySlug prop.
2026-07-30 03:40:20 -03:00
Francisco Ignácio Fontoura Solla
f2bdb1ce3c B77: replace vote wizard ?cenario= ritual with single-screen editor.
Three scenarios on one step with confirm-only validation, legacy URL redirect, and updated tests/docs.
2026-07-30 03:31:57 -03:00
Francisco Ignácio Fontoura Solla
037785b280 B75: wizard mobile header chrome on Mandate Red bar.
Publish flow navigation from CampaignWizardShell via layout-level chrome so wizards no longer stack a second sticky header under the app bar.
2026-07-30 03:26:54 -03:00
Francisco Ignácio Fontoura Solla
37a7062428 B69: simplify the leadership model to exclusive + drop sector/consentNote.
Organization stays by product decision (political branching, e.g. SindMed);
the leadership gains `exclusive` (default true) with list badge and form
checkbox, and sector/sectorNotes/consentNote leave the collection, zod,
forms, list filters/sort, invite autofill and redemption.
2026-07-30 02:32:56 -03:00
Francisco Ignácio Fontoura Solla
af829e1099 fix(campanha): restore vote wizard violation banner on scenario jump (B76)
Keep violationEditedScenario when navigating to the inconsistent scenario
so média shows warning immediately after pessimista breaks order; clear
on draft/shortcut edits. Adds regression unit tests and roadmap B76.
2026-07-30 02:16:12 -03:00
Francisco Ignácio Fontoura Solla
f41b0fbada fix(campanha): restore wizard links on desktop home action strip
Defer pointer capture until drag exceeds the pan threshold so Link
clicks still receive pointerup and navigate to /campanha/acoes/*.
2026-07-30 01:41:21 -03:00
Francisco Ignácio Fontoura Solla
03bcf2e719 B71: polish home search results (bleed hover, list-none, sentence-case headings).
Centralize layout tokens in homeSearchUi.ts so hit rows fill the scroll padding on hover/focus and group lists drop public-site bullet leakage.
2026-07-30 01:27:05 -03:00
Francisco Ignácio Fontoura Solla
31bd488c1e B66: retract home chrome on search focus with animated transition.
Emends B47 so focusing the input clears strip/summary before typing; hardens hit rows against blur-before-click.
2026-07-30 01:21:37 -03:00
Francisco Ignácio Fontoura Solla
34e5c6ed42 B61: wizard vote step with scenario passes and batch save.
Staff can adjust municipality expected votes through média→pessimista→otimista steps with shortcuts, coherence warnings, and a single POST on completion.
2026-07-30 01:16:19 -03:00
Francisco Ignácio Fontoura Solla
a8cb771303 B62+: flatten adversary signal to boolean and harden leader redirect e2e.
The simplify pass dropped the vestigial object wrapper on E11 input and aligned the leader home test with the retrying toHaveURL idiom used elsewhere.
2026-07-29 23:33:46 -03:00
Francisco Ignácio Fontoura Solla
120ed8d9b5 B62: simplify the signal model to type + text (drop fonte/triangulado).
Drops signalSource/triangulated from municipalityUpdate (migration
simplify_municipality_signal_fields), rewrites signal-type labels for the
field team, and treats any adversary signal as confirmed in E11 (P1
triage 1). Also fixes the stale post-B43 leader-home e2e and lowers a
drifted bypass pin.
2026-07-29 23:24:00 -03:00
Francisco Ignácio Fontoura Solla
342e528c87 feat(E10+): Escala e DRY pós-E10
- F1: loadMunicipalityScopeFromDocs evita segunda query em municipalityPageData
- F2: computeAllMunicipalityPotentials memoizado por processo
- F3: TooltipProvider movido para o layout de (app)
- F4: DRY nos filtros multi do mobile em MunicipalityFilters
2026-07-29 23:18:25 -03:00
Francisco Ignácio Fontoura Solla
f6b73b3e63 B60: wizard municipality search step with shared staff loader.
Staff wizards pick a municipality via word-start search, auto-advance with ?municipio=, and reuse searchStaffMunicipalityHits extracted from B48.
2026-07-29 23:10:06 -03:00
Francisco Ignácio Fontoura Solla
67058d0027 B51: add Atividades group to staff home search.
Staff can find activities by title with scoped access, municipality+date secondary, and word-start matching on the existing home-search POST.
2026-07-29 22:57:02 -03:00
Francisco Ignácio Fontoura Solla
38459b7426 B52: add Dobradinhas group to staff home search.
Staff can find state deputies by name or party with municipality counts in the global Início search, mirroring the B49/B50 loader and UI pattern.
2026-07-29 22:52:35 -03:00
Francisco Ignácio Fontoura Solla
0109570b62 B65: anchor Início search and action strip to the mobile bottom.
The B46 flex spacer never filled the scrollport because min-h-full did not propagate; h-full min-h-0 plus home-dock groups the thumb zone above bottom nav.
2026-07-29 22:35:44 -03:00
Francisco Ignácio Fontoura Solla
2c80d9b54f B49: home search Lideranças group on staff Início.
Extends POST /campanha/home-search with leadership hits (word-start, advisor scope, cap 25) and HomeSearchLeadershipGroup below Municípios.
2026-07-29 22:14:30 -03:00
Francisco Ignácio Fontoura Solla
0e73d47a62 B50: home search Assessores group for unrestricted staff.
Word-start on name, municipality count on the row, shared HomeSearchHitRow; POST /campanha/home-search aggregates advisors alongside municipalities.
2026-07-29 22:09:42 -03:00
Francisco Ignácio Fontoura Solla
e733efbe42 B68: curated municipality suggestions on empty home search focus.
Staff see role-ranked shortcuts (advisor portfolio by signal age; CG/candidate by alta priority deficit) via POST mode suggest, without treating empty query as search.
2026-07-29 22:06:02 -03:00
Francisco Ignácio Fontoura Solla
dd23d2a44a B59: chassis do wizard de campanha (shell, rotas /acoes, hrefs no Início).
Staff ganha CampaignWizardShell, rotas /campanha/acoes/<slug> com passo ponte até B60, e strip do Início com links nos fluxos wizard.
2026-07-29 22:02:04 -03:00
Francisco Ignácio Fontoura Solla
4cb59597ff B67: restore fine-pointer drag pan on home action strip
B58 hid the scrollbar but dropped mouse drag-to-scroll; pointer-fine
handlers pan past an 8px threshold while coarse touch keeps native pan-x.
Includes simplify pass (imperative data-dragging, coarse-pointer test)
and pin ratchet for municipalityElectoralBaseline bypass count.
2026-07-29 21:57:53 -03:00
Francisco Ignácio Fontoura Solla
49d5b6da3c B56: staff campaign summary on Início (central votes + E8 coverage). 2026-07-29 17:55:37 -03:00
Francisco Ignácio Fontoura Solla
e462d39fda B13+: concordância LQ, readout de escopo no mapa e DRY do build de agregados.
Rede de segurança para E15, copy honesta no Quadro para assessores, uma varredura federal por ano no artefato e tupla de rank no bundle RSC.
2026-07-29 16:26:43 -03:00
Francisco Ignácio Fontoura Solla
274cde65a7 B58: polish Início action strip and centralize coarse press state.
Short staff labels, hidden scrollbar, circle feedback on hover/press, and long-press pressing lives in useCampaignLongPress for reuse.
2026-07-29 16:10:24 -03:00
Francisco Ignácio Fontoura Solla
05072547cd B48: home search results for municipalities and identity territories.
Staff Início posts to /campanha/home-search with scoped word-start matches, shared readout component, and simplify hardening (messages, fetch busy, malformed response guard).
2026-07-29 16:05:56 -03:00
Francisco Ignácio Fontoura Solla
7bb7b5e261 B47: staff-only home search input with focused mode hiding action strip.
Debounced omnibox chassis and empty results region for B48+; no hit providers yet.
2026-07-29 15:31:46 -03:00
Francisco Ignácio Fontoura Solla
d04f23399f Fill-in: move estimate scenario into municipality list filters.
Cenário lives in MunicipalityFilters with provider wrapping filters and results; roadmap and plan marked delivered.
2026-07-29 15:27:46 -03:00
Francisco Ignácio Fontoura Solla
4658cdd686 B46: anchor home actions in thumb zone via CampaignHomeLayout.
Mobile flex spacer keeps the B44/B45 strip in the lower viewport above bottom nav; md+ keeps actions at the top and reserves searchSlot order for B47.
2026-07-29 15:06:45 -03:00
Francisco Ignácio Fontoura Solla
f9f9a14994 B45: mount persona home action catalog on /campanha.
Staff get six launchers with uncovered-municipalities linking to the sem_assessor list; leaders get two with contacts wired to the shared route constant.
2026-07-29 14:43:50 -03:00
Francisco Ignácio Fontoura Solla
20d230a8c0 Apply Prettier to campaign home action button unit spec. 2026-07-29 14:21:35 -03:00
Francisco Ignácio Fontoura Solla
f03a28fd59 B44: UX-1 home action button and horizontal strip primitives.
Tooltip on fine pointer and long-press drawer on coarse without mounting on Início yet (B45).
2026-07-29 14:20:52 -03:00
Francisco Ignácio Fontoura Solla
926972fec6 B43: blank Início, Quadro staff home, and Contatos for leaders.
Split dashboard and leader contacts into dedicated routes, update nav and gates, and fix revalidation paths after the route move.
2026-07-29 14:02:35 -03:00
Francisco Ignácio Fontoura Solla
2b3d688be3 Simplify E12+ Metropolitano peers extraction.
Centralize sub-row slug helper, drop one-line filter wrappers, and scope E12 sub-row rollups to the Metropolitano region inputs.
2026-07-29 08:24:19 -03:00
Francisco Ignácio Fontoura Solla
be2cbfbb43 Extract Metropolitano TI peer rules into lib for E12+.
Unifies Salvador vs RMS subgroup logic for territory rollups, aggregate class slugs, and the intra-TI capture benchmark so the MAUP-sensitive rule cannot drift across call sites.
2026-07-29 08:15:09 -03:00
Francisco Ignácio Fontoura Solla
158ef22c2c Resolve Salvador ZE in the staff "Onde estou" card (B14 / B8 F2-D1).
Point-in-polygon on the zone mesh opens the accessible ZE directly, with nearest-zone fallback within 30 km before the filtered list.
2026-07-29 07:56:58 -03:00
Francisco Ignácio Fontoura Solla
ee44581ba4 Remove Tipo column and list kind filter from municipality list.
Drop the redundant type column, URL param, and filters while keeping municipality.kind for map and dossier; update tests and delivery docs.
2026-07-29 07:42:47 -03:00
Francisco Ignácio Fontoura Solla
4806c417c8 Engineering Pass 3: consolidate patterns and lock them with guards.
Fold duplicated campanha access, loaders, list/filter/form shells, and scripts into shared modules; reorganize utilities by domain; stabilize e2e; and document prevention-first audit workflow so the same smells cannot creep back in.
2026-07-29 06:11:29 -03:00
Francisco Ignácio Fontoura Solla
77023db99d B34+: scale and DRY after municipality chip lists.
Memoize access reads per req, cache portfolio index, trim chip revalidates, and shrink RSC payload via catalog-backed labels.
2026-07-28 19:49:29 -03:00
Francisco Ignácio Fontoura Solla
ac2390587e B18: saved municipality list filters in localStorage and sidebar submenu.
Bookmarks the canonical list recorte beside Limpar, syncs via useSyncExternalStore, and matches active URLs with a pure href comparator so the app layout never imports the heavy URL serializer.
2026-07-28 19:25:12 -03:00
Francisco Ignácio Fontoura Solla
be0d587005 Dim the tour composer proposal when changing identity territory.
Wire CampaignListPendingBoundary/Results on /campanha/atividades/giros so TI navigation matches list pending UX; pin contract in tourRegionPending unit spec.
2026-07-28 18:41:36 -03:00
Francisco Ignácio Fontoura Solla
0b6843eb80 Polish selected-first filter shells after simplify.
Drop redundant comments, use a Set for mobile checkmarks, and keep behavior unchanged.
2026-07-28 18:37:05 -03:00
Francisco Ignácio Fontoura Solla
38c310a1db Lift selected options to the top in shared header multi-filters.
Freeze order on Popover open so rows do not slide under the pointer, and keep the same presentation on the mobile NativeSelect without a snapshot.
2026-07-28 18:37:05 -03:00
Francisco Ignácio Fontoura Solla
73b9ab84ed Remove the redundant zone list under the campaign map.
After B8 F2 the choropleth already opens each Salvador ZE; drop the textual list and zoneBreakdown from the bundle, keeping the mesh provenance caveat via hasZoneMunicipalities.
2026-07-28 16:00:14 -03:00
Francisco Ignácio Fontoura Solla
524dbb2198 Replace priority badge with flag indicator on municipality list.
Staff sees a Flag + tooltip on the name column and mobile card instead of the destructive “Prioritária” chip, with copy centralized in municipalityLabels.
2026-07-28 14:49:33 -03:00
Francisco Ignácio Fontoura Solla
394826c2f4 Rename municipality list goal column label to Cobertura.
Shorten the list header, mobile card, and sort summary via the B17 label SSoT while keeping glossary and KPI strips on the long metric name.
2026-07-28 14:30:31 -03:00
Francisco Ignácio Fontoura Solla
8cae4adae9 E11 post-rebase simplify: clamp the composed rationale, extract firstFormActionMessage at its 3rd call site, keep metrics server-side. 2026-07-28 14:03:29 -03:00
Francisco Ignácio Fontoura Solla
189ad7a349 E11: suggestion engine v1 — the human-in-the-loop decision queue. 2026-07-28 14:03:29 -03:00
Francisco Ignácio Fontoura Solla
763f64080a Deliver B40: biometric login for /campanha.
WebAuthn platform passkeys: opt-in enrollment after a password login, a
password-less sign-in that mints the same 14-day campaign-token, and
revocation from the profile. The session is minted through Payload's public
pieces (checkLoginPermission, addSessionToUser, getFieldsToSign, jwtSign)
inside a transaction with an advisory lock, so a passkey login neither
bypasses a lockout nor evicts the account's other sessions. The rpID is
derived from NEXT_PUBLIC_SITE_URL and resolves to null when the request host
disagrees, so previews never offer a ceremony the browser would refuse.
2026-07-28 10:59:23 -03:00
Francisco Ignácio Fontoura Solla
0a63621d4b B37 simplify: one membership delta, cap messages named once.
nextStateDeputyIdsAfterMunicipalityMembership was the third byte-for-byte copy
of the same single-id delta — nextAdvisorIdsAfterMembership and
nextStateDeputyIdsAfterMembership differ from it only in which cap they read —
so the algorithm now lives once in nextIdsAfterMembership with the cap as data,
and the three named wrappers just bind their own. The three cloned spec files
became one describe.each suite: coverage went up rather than down, since every
relation now gets the "can go to zero" and no-mutation cases only B37's clone
had, plus an explicit assertion that each wrapper refuses at ITS cap with ITS
message.

That pairing matters because mapCampaignFormActionError matches thrown messages
against safeMessages by exact string, and two of those cap messages were bare
literals in three src places each (thrown, plus two independent allowlists):
rewording any one would have silently collapsed a real refusal into "tente
novamente". Both are constants now, next to their caps, following the
MUNICIPALITY_STATE_DEPUTIES_CAP_MESSAGE precedent already in those same files.
The dobradinha staff message had the same exposure in four places — and
actions/stateDeputy.ts already imported the constant while restating it as a
literal one line below — so it is STATE_DEPUTY_STAFF_MESSAGE (renamed: it
guards create/update too, not just the batch), and the conflict message it
shares with the create route is named too.

The batch action stopped resolving the dobradinha slug before its loop: the
slug exists only to target the revalidate, which the caller already skips when
nothing changed, so on a no-op batch that read was fetched and thrown away —
the guard setLeadershipMunicipalitiesMembershipRecord already had. Its
redundant second dedup is gone (the schema's transform already deduped; the
sort stays, for deadlock-free lock ordering), and the bare 435 shared with the
advisor batch is MAX_MUNICIPALITIES_PER_BATCH.

Two memoization gaps closed, both restoring invariants the codebase had already
established elsewhere. An advisor's scoped .filter() returned a fresh array per
row, defeating the WeakMap that exists precisely to share derivations across
rows, so scopedPortfolioIndex memoizes on (index, addableIds) and every row
gets one array. And LeadershipStateDeputyRelationCell called matchesAtWordStart
per candidate, paying an NFD pass plus three Unicode regexes on both sides per
keystroke over the unpaginated leadership catalog — the cost wordStartFilter's
own doc comment warns about; it now normalizes the query once against labels
cached on options, like municipalityPortfolio.

Rejected with reason: maxItems on the dobradinhas cell. A dobradinha has no
product ceiling on municípios (that is why the bound is the catalog), so a
"generous 30" would invent a rule that refuses a legitimate statewide
dobradinha, and the honest 435 is unreachable when ids are unique.

Also dropped the dead addPlaceholder knob no caller ever set.
2026-07-28 10:25:47 -03:00
Francisco Ignácio Fontoura Solla
da4ca2c713 Deliver B37: editable município chips on the dobradinhas list.
The "Municípios" column of /campanha/dobradinhas reuses
MunicipalityPortfolioCell unchanged, writing the inverted side of the
relation (municipality.stateDeputies) through a single transactional
batch action (setStateDeputyMunicipalitiesBatch): per-município advisory
lock, all-or-nothing on a partial-scope batch instead of a silent partial
apply, no floor (a dobradinha can hold zero municípios). stateDeputiesArraySchema's
cap moved 20 → 435 to fit a whole território batch from the município side.

The post-implementation critique found two P1s, and both were fixed instead
of deferred. First: MunicipalityPortfolioCell and LeadershipStateDeputyRelationCell
had grown the same chip-editing machine twice — optimistic delta, undo, ARIA
1.2 combobox, floor/ceiling, pointer-fine/coarse split — with incompatible
contracts. This was the 3rd call site B34+ was waiting for, so RelationChipCell
is now the single engine and both cells shrank to thin domain wrappers (chip/
search building, FormData, copy — nothing interaction-specific left in either).
LeadershipStateDeputyRelationCell also gained an ownerName prop and its first
dedicated test suite, which it shipped without before this.

Second: an advisor could see, and attempt to remove, a município chip outside
the portfolio they administer, failing silently at the document level.
MunicipalityPortfolioCell now filters the visible chips by addableIds instead
of only gating what can be added, so an advisor sees and can only touch
municípios they administer.

No migration, collection, or Consent. Docs updated: plan as-built, roadmap,
AGENTS.md, project notebook.
2026-07-28 09:34:39 -03:00
Francisco Ignácio Fontoura Solla
ed0fb4065f B17 simplify: inherit header labels, split persist from refresh.
The four *SortableHead islands already fall back to their sort-label record,
so B17's new `label` made the header text a third copy instead of removing the
second one — 16 redundant `children` deleted, leaving only `frescor`, which
diverges on purpose and now reads the record too.

The 3 s "safety net" timer was committing like a close, refresh included, which
recreated the per-toggle route render the 400 ms debounce was rejected for: at a
reading pace every checkbox landed outside the window. The timer now writes the
cookie only; closing repaints. A session that ends where it started refreshes
nothing, and a menu open when the route unmounts saves from the cleanup, since
Radix reports no close event for a sidebar navigation. Both pinned red first.

Also: `columnVisibility` required rather than optional for the tests' benefit,
the redundant per-list cap dropped, `resolveVisibleColumns` no longer copies,
and B29's unused `LeadershipListAccessFilter` export un-exported — knip was
failing on main too.
2026-07-28 09:17:46 -03:00
Francisco Ignácio Fontoura Solla
155f6492d9 B17: column picker on the seven CampaignTable list surfaces.
The picker shipped as a capability of CampaignTable rather than a municípios
feature, so every list that already uses the shared table got it: pass
`columnVisibility` and the table mounts the menu and filters the columns.

Persistence is a cookie, not localStorage, and the reason is a type rather
than a preference: CampaignTableColumn.cell is a function, so the column array
never crosses the server/client boundary and the client cannot rebuild the
table without it. The server filters, so a hidden column does not reach the RSC
payload either. `campaign_columns` carries a listId -> hidden ids map in a
compact form with no JSON and no percent-encoding, because document.cookie and
Next's cookies() disagree about decoding.

Closing the menu is the commit. The first cut debounced at 400 ms, which
batches a burst rather than a reading pace, so unchecking four columns while
reading their labels was four full-route refreshes — on /campanha/liderancas
each one re-pays the ~229 ms / 371-statement render already ledgered P1. The
timer survives at 3 s as a safety net for a menu left open; the price is that
the table behind the menu no longer updates per checkbox.

`defaultVisible` is gone: it was true on 10 columns and false on none, and
storing only the hidden set is what makes a newly shipped column visible to
everyone who never touched the picker. In exchange `label` is required, which
made `head` optional — 24 columns had a head that only restated the new label.
Each surface declares one mandatory column, re-filtered on the server so a
stale cookie cannot leave a row without its only linked cell.

The bug review caught is this codebase's recurring one: the RSC payload of
toggle A arrives inside the window of toggle B, and adopting the server there
undid B and then committed the set without it. Reconciliation now runs only
when no write is pending, pinned red against the previous code.

Municipality column ids are typed against MunicipalityListColumnId now, so the
id, the B22 description and the picker label are one key; the union member
`nivel` was renamed `level` to match the id and the ?level= filter. The
picker's own content comparison is sameIdSet, widened to string ids.
2026-07-28 08:25:16 -03:00
Francisco Ignácio Fontoura Solla
5f11960c3a B32+: share the campaign JSON route wrapper, fetch helper and cell auto-save.
`campaignJsonMutationRoute` now builds every JSON POST route under /campanha —
`isSameOriginRequest` had been copied into five of them, so the CSRF policy was
failing open by omission; a convention guard sweeps all of `src/app` and names
its three exceptions. `postCampaignJson` is the only place that spells
`credentials: 'same-origin'`, and `useCampaignCellAutosave` is the debounce /
abort / revert / flush-on-close machine the quick-edit cells shipped three
times.

The shell keeps the `aria-live` region outside the Popover/Drawer, since closing
is what commits and it was unmounting the node that would announce the result;
`variant="sheet"` took a `footer` slot, which let the signal control drop the
overlay it only kept for one button.

Two bugs the extraction made visible: a revert issued while a different payload
was in flight was dropped silently, and `change()` never cleared a past failure,
which outranks everything the live region would otherwise say.
2026-07-28 08:07:29 -03:00
Francisco Ignácio Fontoura Solla
3536eb02d3 Deliver B29: header sort and filters on the leaderships list.
Bring B15/B16-style URL sort/filter to /campanha/liderancas on the B33 mold, with shared CampaignTableHead filter slot and B18 exhaustive enum canonicalize from /simplify.
2026-07-28 01:43:44 -03:00
Francisco Ignácio Fontoura Solla
a7bc05f3a1 Adopt B42's cell-edit overlay in B34 and fix the optimistic baseline again.
The Drawer half of MunicipalityPortfolioCell now goes through
CampaignCellEditOverlay: the container switches on a `variant` prop, not on
viewport, so the pointer policy stays in CSS while the cell inherits
`initialFocus` (the hand-rolled sheet focused the first chip, a link out of the
row being edited) and the ARIA trigger attributes every other cell has.

The optimistic baseline bug was back in a shape that reads as correct. Applying
the delta functionally is not enough when the fallback is not: `current ??
municipalityIds` still read the render that created the closure, which is the
branch an undo always takes, since the server reconcile clears the optimistic
value first. Undo after a later add rebuilt the row from a set that predated it,
permanently. The baseline is a ref now, advanced synchronously in the draft
branch, and pinned by a spec verified red against the old code.

Also: revert the Drawer body gate (its saving measured as zero, and it animated
an empty box out of view), collapse the word-start matcher to two string tests
now that both sides are normalized, copy the memoized território ids before
handing them to a caller, and revalidate every changed município of an advisor
batch instead of only the last.
2026-07-27 23:59:20 -03:00
Francisco Ignácio Fontoura Solla
812e3a0bee B34: editable município chips on the leaderships list.
The "Municípios" column was dead text; it is now chips that navigate to the
município, with a territory chip when a leadership covers a whole TI, and it is
editable in place. What picks inline vs. Drawer is the `pointer:` media query,
not viewport width: on a fine pointer the editor is always mounted (the "×" sits
at the chip's corner, revealed on hover/focus) and on a coarse pointer a tap
anywhere in the cell opens a Drawer with 44 px targets.

`AdvisorMunicipalityCell` is promoted to `shared/MunicipalityPortfolioCell` and
/campanha/assessores migrates onto it in the same change, so the second call site
is born with the first. One batch action only; the single-id advisor twin is
deleted with its int tests migrated. The anti-goal on always-mounted list inputs
is amended for chip-based relations on fine pointers, not reopened.

Also fixes a latent bug: `CampaignTableHead`'s `description` seam broke any RSC
caller, because its two helpers lived in a 'use client' module.
2026-07-27 22:35:19 -03:00
Francisco Ignácio Fontoura Solla
adb1773be8 Harden the level control now that it also renders as a Drawer.
The migration put this control on a phone for the first time and it was still
mouse-sized: a 28px submit and checkbox rows with no minimum height, where the
siblings size both for touch. Closing the overlay mid-submit was worse — it
unmounts the Alert and the live region while nothing aborts the request, so a
failure went unreported; it now uses the same `reportFailure` the trend and
estimate cells do.

The rest is what the reviewers found around it: the trigger reports `aria-busy`
like every other overlay caller, `canSubmit` carries the override rule that was
stranded in the JSX, the two-level-jump check reads
`ENGAGEMENT_LEVEL_RULES.maxStepsWithoutShock` instead of a literal so E15 cannot
desync the checkbox from the rule behind it, and the route echoes
`updated.engagementLevel` rather than the requested one — with
`overrideAccess: false` Payload drops a field the actor cannot write instead of
refusing, so the request is not evidence the movement landed.

The cell's state stopped being nullable, which meant splitting it from the
route's success payload: a success always names a level, a row need not have
one. And the control joins `overlayCases`, the table that exists to catch
exactly the kind of container mistake this migration could have made.
2026-07-27 22:17:44 -03:00
Francisco Ignácio Fontoura Solla
c7001b75f0 E14: engagement levels N0-N4 and the first allocationDecision writer.
`municipality.engagementLevel` (staff-only, no default) records what the
campaign decided to invest in a município, next to the E8/E10 metrics that
describe what it knows. Moving it is a reallocation decision, so only
unrestricted staff may, every move carries a rationale plus the signals that
would reverse it, and the write files an `allocationDecision` in the same
transaction under an advisory lock taken before the current level is read —
otherwise the snapshot's `from` is a guess. The enum gains `movimento`: a level
move is neither `aceita` nor `descarta`.

No backfill. Null is "Sem nível", and filtering by it is the triage queue.
That is also why the list sorts `nivel` in memory rather than in the query:
Postgres puts NULLs first on DESC, so a native sort would open "N4 primeiro"
on a page of municípios nobody has decided about yet. The filter stays native.

The hysteresis rules from research §6.8 are pure and client-safe, so the
Popover explains a block before submitting and the server re-runs them anyway;
a blocked move answers 409 with typed violations and the override is recorded
with exactly the rules it contradicted.
2026-07-27 21:19:33 -03:00
Francisco Ignácio Fontoura Solla
d5b4af2d98 E13: visit eligibility checklist and the tour composer.
Five conditions per município (volume, headroom, rede, janela, encaixe) with
their reason always visible, plus a staff-only composer at
/campanha/atividades/giros that turns an âncora + satélites + semente into draft
activities in one transaction. No `visita` kind and no `tour` entity:
`deputyPresent` already marks the candidate's own agenda.
2026-07-27 21:04:04 -03:00
Francisco Ignácio Fontoura Solla
59d89ce287 B42: Drawer quick-edit and a tappable card on the mobile municipalities list.
Tendência, Assessores and Votos estimados now share one container
(CampaignCellEditOverlay: Popover on md+, bottom Drawer below), the card drops
to the table's density, and the whole card navigates through a stretched link
instead of a second "Abrir município" button.
2026-07-27 20:49:41 -03:00
Francisco Ignácio Fontoura Solla
6c0c5e87ce B41: horizontal scroll and sticky Município on the municipalities list.
Mirror TerritoryList so wide staff columns can scroll on md+ without losing the row identity.
2026-07-27 10:16:16 -03:00
Francisco Ignácio Fontoura Solla
211276b4e4 B32: editar Status de apoio na célula da lista de lideranças
- Popover + NativeSelect sempre montado na coluna Status de
  /campanha/liderancas, sem toggle de modo de edição
- Auto-save (~150ms) via POST /campanha/liderancas/support-status,
  reusando isSameOriginRequest/campaignJsonMutationRoute (B24/B27),
  sem revalidatePath
- Extrai leadershipStaffEditSafeMessages (adotado também em
  liderancas/formActions.ts e nova/formActions.ts) e
  utilities/leadershipLabels.ts (3º call site de supportStatusLabels,
  agora fonte única também para SupportStatusBadge)
- Novo e2e tests/e2e/campaignLeaderships.e2e.spec.ts; setup.e2e.spec.ts
  prewarma as rotas novas
- Fecha o Adiado com gatilho do B29; docs/plans, notebook e roadmap
  atualizados

Gate: tsc/lint/format/knip(P3 pré-existente)/check:cycles/unit+int/e2e
verdes; two /simplify passes (labels/estado duplicado/isSupportStatus;
flush-on-close ao fechar o popover, comentário órfão de summary/
campos summary mortos removidos de SupportStatusBadge).
2026-07-27 09:56:09 -03:00
Francisco Ignácio Fontoura Solla
bcb58c2162 B33+: close the /simplify rounds on the shared list filter hook
Follows the URL when the committed query changes with no debounce pending, so
Back/Forward and the empty state's clear anchor update the search box without
overwriting text the user is still typing. That removed the last reason for
/campanha/municipios to remount its filters, so the key= (and its now orphaned
buildMunicipalityFiltersKey) is gone and the three lists share one mounting
policy — the dedup guard was dead code on municípios while it remounted.

Reuse: setSearch + scheduleSearch were always called as a pair, so they collapse
into onSearchChange (hook surface 8 -> 6 names); MunicipalityFilters reuses the
buildMunicipalityFilterHref it was reimplementing inline; dobradinhas gains the
sibling buildStateDeputyFilterHref for the four sites pinning page 1 by hand;
municípios gains the active-filter summary its mobile block was missing.

Also drops an export with no consumer that was failing knip in CI (seo.ts).
2026-07-27 09:39:15 -03:00
Francisco Ignácio Fontoura Solla
abe37cbc5c B33+: shared list filter navigation hook
Extract the debounce/dedup/transition scaffold that StateDeputyFilters,
TerritoryFilters and MunicipalityFilters each reimplemented into
useCampaignListFilterNavigation, and unify the two behaviors that had
silently diverged: touching a filter mid-typing used to discard the
pending search on dobradinhas and territorios, and the debounce timer
captured a stale state, so a sort clicked within its window was reverted
a second later. Also folds the six copies of the pending-transition
fallback into useCampaignListTransition and drops a redundant aria-live
that made the municipality summary the third live region on its page.
2026-07-27 09:05:08 -03:00
Francisco Ignácio Fontoura Solla
3892c2e0a4 Add remembered campaign sessions
Keep campaign JWT and cookie lifetimes aligned while revoking the active server session on logout.
2026-07-27 08:57:26 -03:00
Francisco Ignácio Fontoura Solla
98c334785e Tighten formActions guard: allowlist real exceptions and require call sites.
Also rethrow non-FormDataBoundaryError from the activity lifecycle id pre-parse.
2026-07-27 08:33:41 -03:00
Francisco Ignácio Fontoura Solla
440c15f132 C8 F4b: widen formActions guard and migrate sibling ladders.
Cover *FormActions.ts (+ taskActions allowlist) so route siblings cannot drift past the shared wrappers; migrate activity/municipality ladders onto runCampaignFormAction.
2026-07-27 08:33:41 -03:00
Francisco Ignácio Fontoura Solla
fa75c355a0 Make public SEO resilient when the metadata global is empty.
resolveSiteMetadata fails open on missing URL/text so next build no longer crashes on a fresh or poisoned metadata cache.
2026-07-27 08:20:33 -03:00
Francisco Ignácio Fontoura Solla
2c30944593 B8+: escala das malhas em unidades reais, contrato do map key e degradacao
F1 — a tolerancia de simplificacao e os digitos de quantizacao viraram
politica em unidades de solo em scripts/lib/topology.mjs, consumida pelos
dois scripts de geometria. Os quantis 0.35 copiados cortavam as 19 ZE de
Salvador a ~0.6 m2 e os 417 municipios a ~1.78 km2; com tolerancia
absoluta e a grade de ~103 m do estado, a malha das zonas caiu de 480 para
30.2 pontos/feature. bahia-municipalities segue byte-identico e o tripwire
de bytes deu lugar a pontos/feature.

F2 — featureMapKey passou a ser asserido sobre features reais das tres
malhas comprometidas (417 codarea, 27 code, 19 slug), e o comentario de
MAP_KEY_PROPERTIES agora diz o que o codigo faz: as propriedades sao
mutuamente exclusivas, nao ordenadas por especificidade.

F3 — a malha das zonas e um detalhe sobre o mapa, nao o mapa: se o chunk
falha, Salvador cai no poligono municipal que ja esta desenhado embaixo e
o resto da Bahia continua funcionando, com o motivo logado. A ressalva de
aproximacao saiu do gate de comparacao e passou a depender de
bundle.hasZoneMunicipalities, derivado no servidor de municipality.kind.

F4 — orfaos deletados: os dois acessores sem chamador, isZoneMapKey (o
servidor ja sabe a regra), o wrapper simplifyTopology e o helper
polygonCount com um branch inalcancavel. MunicipalityMapNavigation
colapsou em string | undefined e MunicipalitiesByMapKey em
Record<string, string>.
2026-07-27 01:59:36 -03:00
Francisco Ignácio Fontoura Solla
bb5ac362ed Add users.roles so Payload /admin can separate editors from admins.
Editors publish post/tag/media without PII or campaign access; existing users backfill as admin.
2026-07-27 01:54:29 -03:00
Francisco Ignácio Fontoura Solla
7b70322118 Document B8 F2 and apply the simplify pass findings.
Records the Salvador zone delivery where the next agent will look for it —
roadmap, plan as-built, notebook and the geometry/map sections of AGENTS.md —
including the two measurements that are easy to re-derive wrongly (the bay
water in Salvador's municipal polygon, and ZE 16 wrapping ZE 14, which is why
neither an area ratio nor a centroid check is a valid overlap test).

Simplify fixes: the build script re-indexes simplified geometries by a unique
id instead of neighbourhood name, so a repeated name in a future mesh cannot
silently collapse two polygons inside a committed artifact; a stale comment
claiming zone polygons do not exist; and two int assertions that the Fase 0
catalog hydration turned tautological.

Ledgers the Aikido outage: the scanner returns Opengrep exit 2 for any input,
so the mandatory per-change scan was skipped, not passed.
2026-07-26 23:25:42 -03:00
Francisco Ignácio Fontoura Solla
84e796ab49 B8 F2: polígonos das ZE de Salvador e re-keying do coroplético por map key
Salvador era um polígono só carregando os votos da cidade inteira, então o
coroplético não conseguia responder "qual pedaço de Salvador está fraco / é
meu". As 19 ZE ganham polígono próprio, dissolvido da malha de bairros do IBGE
(Censo 2022) pela lista de circunscrição da RA 02/2017 do TRE-BA, e o mapa passa
a pintar por map key (slug para as zonas, ibgeCode para o resto) em vez de
codarea. O polígono municipal de Salvador continua desenhado por baixo, sem dado
e sem interação, para não abrir buraco no contorno do estado.

Aproximação declarada na copy e no cabeçalho do script: o TRE publica a
circunscrição como lista de bairros, então o bairro é o átomo geométrico —
Periperi, que a resolução corta na Rua das Pedrinhas, vai inteiro para a ZE 17.
competitiveRank segue por cidade (o artefato TSE só tem federalRankByIbgeCode).

Sem migration, sem collection, sem action, sem Consent.
2026-07-26 23:08:09 -03:00
Francisco Ignácio Fontoura Solla
f06e573f15 E12: camada TI com rollups MAUP e benchmark intra-TI.
Estende territórios com cobertura/captura/classe agregadas e compara captura do município aos pares do mesmo TI no card Conta da cadeira.
2026-07-26 21:26:47 -03:00
Francisco Ignácio Fontoura Solla
ba90a3b2dc Merge branch 'cursor/6b2ac7b2' 2026-07-26 21:16:40 -03:00
Francisco Ignácio Fontoura Solla
06ea1ff295 B36: chips de lideranças na lista de dobradinhas
Espelha a aresta leadership.stateDeputies do B31 do lado do deputado, com célula compartilhada bidirecional, loadLeadershipOptions e casca própria de formActions.
2026-07-26 21:15:44 -03:00
Francisco Ignácio Fontoura Solla
162d4c10fe perf(campanha): roda as funções em gru1 e encurta o waterfall de queries
As funções rodavam no default iad1 (Virgínia) enquanto o Neon fica em
sa-east-1 (São Paulo): cada query do Payload atravessava o equador duas
vezes (~120 ms por ida-e-volta) e uma página do /campanha faz uma dúzia.
Medido em produção: 410-470 ms de TTFB para um 307 que não toca o banco.

- vercel.json fixa regions: [gru1], mesma região AWS do banco (Hobby
  permite uma região, mas permite escolher qual).
- loadStatewideSuggestedGoals passa a ser exportada e é disparada dentro
  do Promise.all que já lê o escopo, na lista e no dashboard: o read do
  global campaignGoals saía sozinho no fim da requisição.
- loadMunicipalityScope lê os pledges em paralelo quando o ator é
  irrestrito e não há filtro (aí o IN de 435 ids não exclui nada);
  qualquer escopo mais estreito continua esperando os ids.

Débitos medidos no caminho e registrados em TECH-DEBT: o fan-out de
leituras do ator em getFreshCampaignUser (~5 por render) e a leitura
dupla em authenticateCampaignToken.
2026-07-26 21:00:06 -03:00
Francisco Ignácio Fontoura Solla
d6455df5e7 B30: convite WhatsApp na lista de lideranças
Staff gera convite de autopreenchimento na coluna de ações da lista, reutilizando createCampaignInvite e alinhando validação de celular ao link de contato do B28.
2026-07-26 20:47:11 -03:00
Francisco Ignácio Fontoura Solla
ef3d5b6c22 B31: coluna de dobradinhas na lista de lideranças
Coluna "Dobradinhas" em /campanha/liderancas com chips clicáveis
para /campanha/dobradinhas/<slug> e edição via Popover+Command
(padrão B27, escrita via server action delta + lock, como B19).

- LeadershipRowViewModel.stateDeputies (StateDeputySummary[]) via
  loadStateDeputySummaries, substituindo stateDeputyNames.
- setLeadershipStateDeputyMembership(Record): delta com
  withPayloadTransaction + acquireTextAdvisoryLocks + revalidatePath.
- nextStateDeputyIdsAfterMembership (lib/leadershipStateDeputyMembership.ts),
  espelha nextAdvisorIdsAfterMembership.
- loadStateDeputyOptions estendido com plainName/party/slug.
- Catálogo entregue uma vez por tabela via leadershipColumns(options)
  (columns factory, mesmo precedente de municipalityListColumns).

/simplify (1ª rodada): corrige revert otimista por delta (não pelo
snapshot inteiro), remove fallback silencioso na adição, extrai
sameIdSet compartilhado com MunicipalityListAdvisorsControl (B27), e
evita leitura+revalidação supérfluas no caminho no-op da action.

/simplify (2ª rodada, pós-rebase): troca o StateDeputyOptionsProvider
(Context) inicial pela columns factory acima — a 1ª rodada tinha
validado o Context por falta da evidência de que MunicipalityList.tsx
já resolve o mesmo problema sem Context; corrige revalidatePath para
o id concreto em vez do template dinâmico '/campanha/liderancas/[id]'
(que invalidava toda ficha a cada toggle); remove non-null assertion
movendo a guarda para dentro do setCurrent updater.

Gate verde: tsc, lint --max-warnings=0, format:check, unit 530/530,
int 406/406 (incl. os 6 de campaignLeadershipStateDeputyMembership),
build. Aikido: 0 achados nos arquivos novos/editados.
2026-07-26 19:50:15 -03:00
Francisco Ignácio Fontoura Solla
a60200cf6c B25: link do território na lista de municípios
Fecha a navegação inversa para /campanha/territorios com âncoras estáveis compartilhadas entre lista e destino.
2026-07-26 18:59:49 -03:00
Francisco Ignácio Fontoura Solla
2086ed50e9 B38: sidebar recolhido em tablet no shell /campanha
Rail offcanvas com cookie, default colapsado em viewport tablet e trigger visível a partir de md; fecha o item no roadmap.
2026-07-26 18:40:31 -03:00
Francisco Ignácio Fontoura Solla
5ace1a04b2 simplify(B14): corrige o mecanismo da fronteira, estado unsupported e duplicatas
Terceira rodada de /simplify sobre o B14, com dois enunciados errados corrigidos:

- A propriedade da fronteira compartilhada NAO vem da ordem do `.find()`, e sim
  da convencao meio-aberta do ray casting (`point.lng < crossingLng`): a aresta
  pertence ao poligono cujo interior fica a leste (ao norte, se horizontal). Tres
  comentarios diziam o contrario, e o do unit test dizia "oeste" enquanto
  afirmava `east`.
- O plano dizia que o `Promise.all` esconde o decode da malha atras do fix de
  GPS. Nao esconde: o decode roda no topo do modulo, quando o chunk executa. O
  paralelo continua certo por outro motivo (decode ~2 ms; sequencial jogaria os
  42 KB no caminho critico quando o fix vem do cache).

Simplificacoes: `unsupported` virou estado proprio, entao `failureCopy` deixa de
ser anulavel e caem duas leituras defensivas inalcancaveis; "Voce esta em " era
escrito duas vezes (frase sr-only + JSX), o que anulava justamente a invariante
que o comentario alegava; `zoneCityHrefs` virou `Partial` porque a busca falha
para os 416 municipios sem zona e o guard existia sem o tipo exigir;
`MunicipalityForIbgeIndex` era duplicata campo-a-campo de `AccessibleMunicipality`;
`kmFormatter` duplicava `oneDecimalFormatter`; `RecentlyVisitedCard` carregava
tres props que nenhum caller passa (dez condicionais mortas); e o bbox de feature
estava escrito em dois specs (agora `tests/helpers/featureBounds.ts`, compartilhado
por vitest e Playwright).

Copy: fora da Bahia sem nada por perto dizia a mesma coisa em duas frases e nunca
dizia que voce estava fora do estado. Agora o headline afirma o fato e o detalhe
reusa "Nenhum municipio da sua carteira por perto.".

Tambem limpa um achado de knip herdado do main (`MunicipalityPoliticalTrendViewModel`
exportado sem uso) e registra no plano o caminho afiado do debito de payload RSC
mais dois adiados com gatilho.
2026-07-26 18:03:44 -03:00
Francisco Ignácio Fontoura Solla
e482005ce0 B14: municipio mais proximo (atalho geo no Inicio staff)
Card "Onde estou" no dashboard staff: point-in-polygon contra a malha
municipal que o painel do mapa ja carrega, sem artefato de centroides,
sem migration, sem collection, sem server action e sem Consent -- a
posicao e lida por navigator.geolocation e casada no browser, nunca sai
do aparelho.

- src/lib/municipalityProximity.ts: haversine, ray casting, centroide
  ponderado por area e uniao discriminada
  (inScope | zoneCity | outOfScope | outsideBahia)
- src/utilities/campaignGeolocation.ts: flag de sessao, leitura guardada
  de permissao e requestCurrentPosition com falha tipada
- Salvador nao ganha ZE chutada: link para a lista filtrada, com href
  serializado no servidor (importar o serializador canonico no cliente
  custava 21 kB de First Load JS)
- fallback fora da carteira ordenado por centroide, teto de 150 km
- fix grosseiro (>10 km) mostra o resultado com ressalva de precisao

/campanha First Load JS: 265 -> 268 kB.
2026-07-26 17:33:33 -03:00
Francisco Ignácio Fontoura Solla
8c55daffb2 B33: ordenação e filtro no header da lista de dobradinhas
- stateDeputyListUrl.ts / stateDeputyListFilters.ts: contrato de URL/filtro
  próprio (sort/dir, filtro de Partido multi + sentinela "Sem partido"),
  espelhando municipalityListUrl.ts (paginação/resolveListUrl) e
  territoryListUrl.ts (sort keys/labels)
- stateDeputyData.ts: where/sort resolvidos no banco + facet de partido
  (loadStateDeputyPartyFacet), select estreito na query principal
- StateDeputySortableHead / StateDeputyHeaderFilter / StateDeputyFilters:
  wrappers finos sobre o chrome compartilhado (shared/CampaignSortableHead,
  CampaignHeaderFilterPopover), paridade mobile incluída
- dobradinhas/page.tsx: redirect canônico em duas fases, CampaignTable
  sempre montada com caption + empty state, resumo de ordenação aria-live
- Correção pós-entrega (design review): "Sem partido" no mobile + caption
  da tabela + copy plural do empty state
- /simplify (1ª rodada): buildStateDeputyPartyOptions compartilhado, remove
  discriminante filterParam redundante, dedupe/if mortos em
  stateDeputyListUrl.ts
- /simplify (2ª rodada, pós-rebase): remove cast/type morto em
  stateDeputyData.ts, stateDeputyColumns usa StateDeputyFilterOption em vez
  de duplicar a forma, inlinea parsePartiesParam; extrai truncatedNamesLabel
  compartilhado em campaignListUrl.ts (3º call site — municipalityListFilters
  e territoryListFilters tinham a mesma função com nomes distintos)

Gate verde (tsc, lint, format, check:cycles, 396 testes, build); Aikido
0 achados nos 7 arquivos tocados nesta rodada. Docs:
docs/plans/ordenacao-filtros-lista-dobradinhas.md, docs/roadmap.md,
.cursor/rules/projects/nucleos-eleitorais.mdc.
2026-07-26 16:21:40 -03:00
Francisco Ignácio Fontoura Solla
b3283ee061 B24: auto-save e justificativa no popover de Tendência.
Remove o botão Salvar da lista de municípios; grava via POST political-trend sem revalidar a lista, com flush coalescido e feedback na célula.
2026-07-26 14:05:36 -03:00
Francisco Ignácio Fontoura Solla
66feecd66b simplify(B13): corrige classe agregada, bolhas em pane proprio e registra debitos
O /simplify da escala relativa do mapa achou um bug semantico real em
computeAggregateTerritorialClass — somava o headroom dos slugs e comparava
contra a mediana de UMA unidade, classificando um grupo grande como expansao
quando ele e marginal. A mediana agora escala por slugs.length, com teste.

As bolhas sairam do centro do bounding box para o centroide real e ganharam
pane proprio (z-index 450), porque o bringToFront do hover as escondia atras
do poligono. Legenda e pintura passaram a compartilhar o mesmo predicado,
a copy dos quantis parou de prometer faixas de igual largura, e o subtitulo
do painel parou de dizer "em 2026" com 2022 selecionado (defeito do B11).

Fonte unica onde havia duas: NO_DATA_FILL, o raio das bolhas e
formatPlacementOrdinal (ex-formatMunicipalityVoteRank, agora client-safe em
electionFormat.ts).

Os quatro debitos maiores que o cleanup viraram o fill-in B13+
(docs/plans/escala-dry-pos-b13.md); a flakiness dos e2e, reproduzida em main
limpo, foi para o ledger com a causa afinada.
2026-07-26 05:12:37 -03:00
Francisco Ignácio Fontoura Solla
29f45e093a B13: escala relativa no mapa dos Municipios (quantis/LQ/posicao) + artefato TSE v3
O coropleto do Inicio era quase monocromatico porque 0-100% dos validos comprime
uma disputa cujo topo estadual e 4,71%. Tres escalas relativas ao proprio
candidato, todas em classes discretas: quantis (novo default), padrao proprio
(LQ) e posicao no municipio, ao lado de "% dos validos" (B11) e "Total (votos)".

Artefato v3: federalRankByIbgeCode com a colocacao entre todos os candidatos com
votos > 0 na geografia (2014/2018/2022), chaveado por ibgeCode porque e o que o
mapa desenha. Fica no artefato commitado, nao em query de request, que varreria
~100 mil linhas por ano para um dado imutavel. 534 -> 623 KB (orcamento 700 KB).

LQ virou 5 classes nas ancoras do E10, nao diverging: o plano pedia as duas
coisas e se contradizia. Vermelho x azul continua significando "Solla x
adversario" e segue exclusivo do compare.

computeAggregateTerritorialClass classifica um grupo de slugs como um territorio
so, somando insumos e rodando o mesmo classificador (LQ e razao; a media de
razoes nao e a razao das somas) - o E12 herda o helper. TERRITORIAL_CLASS_ANCHORS
mudou para src/lib/territorialClassAnchors.ts para nao arrastar o artefato TSE
ao bundle do cliente.

Quantis sobre o escopo de acesso do usuario, degradando para 3 classes abaixo de
10 municipios com a legenda declarando a contagem efetiva. Rank indisponivel em
2026 (dado TSE); LQ em 2026 reusa o padrao estadual de 2022.

Bolhas opcionais por validos projetados (area proporcional ao valor, cor da
classe E10, interactive: false). 417 marcadores medidos antes de otimizar - nao
derrubaram o frame, entao o corte para top-K nao foi feito.

Sem migration, sem collection, sem server action; bundle client de /campanha
byte a byte igual.
2026-07-26 04:22:56 -03:00
Francisco Ignácio Fontoura Solla
a4b62c1a9a fix(test): drop obsolete municipalitySlug/formAction props from MunicipalityListAdvisorsControl test after B27 rebase merge 2026-07-26 03:34:11 -03:00
Francisco Ignácio Fontoura Solla
7c50e29aa7 B27: combobox + chips + auto-save no popover de Assessores da lista de municípios
Replaces the checkbox-list + "Salvar assessores" button in the
Assessors popover (/campanha/municipios) with removable chips + a
Command combobox, delta-based auto-save (one advisor add/remove per
request, never the whole array), and optimistic UI with request
sequencing to resolve out-of-order responses correctly.

- New POST /campanha/municipios/advisors JSON route (mirrors
  expected-votes/), backed by setMunicipalityAdvisorMembership(Record)
  in actions/municipality.ts (text advisory lock + transaction).
- nextAdvisorIdsAfterMembership moved to
  src/lib/municipalityAdvisorMembership.ts with unit tests.
- Shared isSameOriginRequest (src/utilities/sameOriginRequest.ts) and
  uniqueRelationshipIds (src/utilities/relationship.ts) helpers,
  de-duplicating the same-origin CSRF check and the
  Set+map(relationshipId)+filter pattern across advisor call sites.
- MunicipalityListAdvisorsControl.tsx rewritten: chips, Command
  combobox, per-item pending affordance, rollback on error, and a
  request-sequence guard so only the most recently sent delta's
  response is adopted when concurrent optimistic updates settle.
- e2e coverage for the combobox auto-save flow; setup prewarms the new
  POST-only route to avoid Playwright dev-server HMR aborting the
  first request.
- Plan and roadmap marked as delivered (B27).
2026-07-26 03:31:51 -03:00
Francisco Ignácio Fontoura Solla
d4b7aab50e B23: tooltip de conteudo nas celulas Assessores/Tendencia de /campanha/municipios
- openOnTouch/disabled em CampaignHoverTooltip para conviver com Popover
  sem roubar o tap nem sobreviver atras dele; falsy content agora renderiza
  o trigger sem wrapper (colapsa o ternario nos dois consumidores)
- formatAdvisorNamesTooltip + advisorEntriesFromIds compartilhado
  (MunicipalityAdvisorAvatarStack) para dedupe entre lista e controle
- MunicipalityListAdvisorsControl: hover mostra nomes completos (coordenador)
- MunicipalityListTrendControl: hover mostra a justificativa (politicalTrend.note,
  E4R), com whitespace-pre-wrap preservado; leitura tambem dentro do Popover em toque
- testes unit dos wrappers/no-op e do helper

/simplify (pos-rebase): corrigido HEADER_TOOLTIP_COUNT (4->10) no teste da
celula de assessores, que ficou obsoleto depois do rebase absorver o B22
(10 tooltips de header no thead staff) - o teste passava a comparar 5 vs 11
sem essa correcao; formatAdvisorNamesTooltip agora tipa ReactElement | null
em vez de ReactNode, removendo os casts 'as React.ReactElement' no teste;
comentarios de baixa informacao removidos/enxugados em CampaignHoverTooltip
e nos dois controles; docs: plano, roadmap e notebook do projeto marcados
como entregues.

Gate verde: tsc, lint (0 warnings), format, 446 unit, build anterior verde.
Aikido: 0 findings.
2026-07-26 03:21:45 -03:00
Francisco Ignácio Fontoura Solla
fb4ba831c8 B22: explicacao por coluna no header das listas — description em CampaignTableHead/MunicipalitySortableHead, campaignConceptOneLiner como fonte unica E18, municipalityColumnDescriptions cobrindo as 11 colunas de /campanha/municipios
/simplify: extrai campaignHoverExplanationClassName/campaignHoverTooltipAlign para CampaignHoverTooltip.tsx (elimina duplicacao entre CampaignTableHead, MunicipalitySortableHead e MunicipalityGoalAccountCard), troca non-null assertion de campaignConceptOneLiner por lookup em Record, corrige copy da coluna Assessores (coordenador, nao candidato) e restaura comentario de invariante do sort de goalCoverage
2026-07-26 02:58:54 -03:00
Francisco Ignácio Fontoura Solla
63fe4431bf E10: classificacao territorial relativa (Reduto/Expansao/Manutencao/Marginal/Sem base) sobre o artefato TSE, com coluna+sort+filtro na lista, badge no card da conta da cadeira e na capa do dossie; seam generico cellTooltip na CampaignTable e promocao do CampaignHoverTooltip para shared 2026-07-25 21:59:20 -03:00
Francisco Ignácio Fontoura Solla
a9e1900290 B21: cria página própria dos territórios
Leva a leitura dos 27 Territórios de Identidade para o sistema de listas com estado canônico na URL e remove o painel duplicado do Início.
2026-07-25 21:50:24 -03:00
Francisco Ignácio Fontoura Solla
619666dd51 C13 pos-simplify: fecha os residuos da varredura e estreita os guards
O rename mecanico deixou um defeito silencioso e alguns fosseis: o fixture de
recentVisits perdeu o ${index} (todas as entradas com o mesmo label, invisivel
porque o label nao era asserido) e access/shared.ts guardava "action plan" em
prosa, forma que o guard de vocabulario nao cobria. O padrao agora tolera espaco
e a allowlist encolheu: o comentario de campaignMigrationReconciliation cita
activity e o arquivo saiu da excecao.

Na migration, a entrada action_plan_municipality_idx era inalcancavel (nenhuma
migration jamais criou esse nome) e saiu, deixando up() e down() com os mesmos
48 pares; as duas buscas de FK passam a usar to_regclass, que retorna NULL em vez
de lancar -- a unica consulta que contradizia a invariante do proprio cabecalho.

O detalhe de organizacao para de derivar o rotulo de status a mao e reusa
ActivityStatusBadge, com status tipado como ActivityStatus no loader.

Debitos do /simplify registrados: C8 F4b (o guard de formActions so inspeciona
arquivos chamados exatamente formActions.ts) no plano do C8; defers e descartes
com gatilho no plano do C13.
2026-07-25 21:29:32 -03:00
Francisco Ignácio Fontoura Solla
7b93c232ff C13: renomeia "Plano de Acao" para "Atividade" em toda a vertical /campanha
Collection actionPlan -> activity (labels "Atividade"/"Atividades"), campo
campaignDemand.actionPlan -> activity, rota /campanha/planos ->
/campanha/atividades (criacao em atividades/nova), sidebar "Atividades" e
rotulos alinhados a entidade ("Tipo de atividade", "Origem da atividade",
"Resultado da atividade"). Valores de enum kind/status/origin inalterados.

Schema movido por migration data-preserving escrita a mao
(20260725_213000_rename_action_plan_to_activity): 4 tabelas, 3 enums, 2
colunas, 39 indices (4 pkeys inclusos), 14 FKs e 2 sequences, cada statement
guardado na existencia do objeto de origem, com down() simetrico. O gerador do
Payload emitiria DROP+CREATE e nao ve o indice parcial escrito a mao
action_plan_upcoming_start_at_idx, entao a lista veio de
pg_indexes/pg_constraint/pg_type/pg_sequences e o snapshot .json foi derivado
do anterior por transformacao textual -- validado por migrate:create __probe
sem mudanca de schema. A mesma migration alinha os 8 indices fosseis %plaza%
que a remodelagem de municipios deixou (renomeou as colunas plaza_id, nao os
indices).

Guard: o describe "banned campaign terminology" de
codebaseConventions.unit.spec.ts virou tabela de termos varrendo src+tests+
scripts (migrations fora -- historia congelada), banindo os compostos
actionPlan, a copy "plano de acao" e a rota antiga, mas nao plan/plano isolado
(colidiria com os planos de assinatura da Fase 2). Allowlist: o proprio spec,
scripts/generate-remodel-municipalities-migration.mjs e o comentario historico
em campaignMigrationReconciliation.int.spec.ts.

Plano: docs/plans/renomear-plano-acao-para-atividade.md
2026-07-25 20:50:38 -03:00
Francisco Ignácio Fontoura Solla
228eb36e90 B26: registrar sinal tipado a partir da célula Último sinal na lista de municípios.
Fecha o laço da fila E9 — Popover/Drawer cria municipalityUpdate kind sinal sem sair da varredura; submit explícito multi-campo; revalidate da lista.
2026-07-25 20:42:01 -03:00
Francisco Ignácio Fontoura Solla
68bcaa3ab9 B20: municípios prioritários no Início com déficit E9 e CTA para a lista.
A seção deixa de truncar seis prioritárias sem critério; amostra oito por cobertura central, mostra o total e linka ?priority=alta alinhado à fila de alocação.
2026-07-25 20:01:51 -03:00
Francisco Ignácio Fontoura Solla
4bbabb7d5e Guardas programaticas: fronteira lib/ (no-restricted-imports), ban de consent id hardcoded, codebaseConventions.unit.spec (server-only, copy Praca/Nucleo, listas legadas do eslint, wrappers de formActions), ciclo do payload.config quebrado (documents/globals divididos em tags+reads), timeout global do int suite, personName schema-valido, consent drafts Onda 0 p/ Municipio 2026-07-25 18:02:02 -03:00
Francisco Ignácio Fontoura Solla
e6f7c2e70f Tooling: prettier como autoridade de formatacao (organize-imports deterministico, scripts format/format:check, normalizacao mecanica de 312 arquivos; artefatos gerados e migrations excluidos via .prettierignore) 2026-07-25 17:22:20 -03:00
Francisco Ignácio Fontoura Solla
304eb34b64 Pass 2 D3: consent do WhatsApp resolvido por chave estavel (fim do consent: 2)
- submitWhatsapp resolve Consent.key = 'whatsapp-inscricao' via requireConsentByKey e FALHA FECHADO com mensagem clara enquanto o documento nao existir
- migration 20260725_170000 (data-only, idempotente, guardada): tagueia o documento vivo id 2 (key vazia/'') com a chave estavel; no-op se ja houver documento com a chave ou se id 2 tiver outra chave; NAO inventa texto juridico (politica Onda 0)
- chave em lib/campaignConsentKeys + lease key nas fixtures; int spec pina fail-closed + caminho feliz (contact + subscription -> consent.id)
- ATENCAO DEPLOY: revisar o SQL antes do push (aplica em producao no build da Vercel)
2026-07-25 16:02:57 -03:00
Francisco Ignácio Fontoura Solla
c01467547d Pass 2 W4b: zero exports mortos; knip exports/types/enumMembers em ERROR no CI
- ~190 findings zerados: knip --fix (grafo corrigido: tests como entries, migrations como consumidores) + delecao guardada dos orfaos (git grep -w antes de deletar) + reparos manuais dos corpos arrow pendurados
- pecas nao usadas do kit ui deletadas (PaginationLink/Next/Previous, familias Sidebar/Combobox/Sheet/Drawer/Dialog etc.) - shadcn add recoloca se precisar
- acoes mortas removidas (updateCampaignDemandDetails*, *Record orfaos), schemas/tipos mortos (campaignDemandDetailsUpdateSchema, Contact do zod), helpers mortos
- tipos de eleicao derivam direto das *_OPTIONS (consts intermediarias eliminadas)
- knip.json: rules exports/types/enumMembers=error, duplicates=off (unicos hits: 6 aliases deliberados canUpdateX=canReadX); hints aplicados (payload.config entry redundante, ignores de tsx/sharp/playwright/payload-types desnecessarios)
- ledger: linha P2 dos unused exports fechada
2026-07-25 15:51:52 -03:00
Francisco Ignácio Fontoura Solla
267218b05a Pass 2 W4d: runCampaignFormAction (stay-on-page) fecha o C8 F4
- novo wrapper ao lado de runCampaignRedirectFormAction: execute -> success spread (message + extras tipo advisorId/supporterId); erro mapeado por mapCampaignFormActionError com echo de values/revision
- 13 acoes migradas em 7 arquivos: dobradinhas/[slug], organizacoes/[slug], municipios editar, liderancas/[id], assessores (5), demandas/[slug] (3), apoiadores/novo, convite autofill
- early-returns viram throws: mensagens seguras (Campo invalido, Selecione ao menos um municipio, Status invalido) e FormDataBoundaryError p/ erros de campo (cost, receipt) - estados visiveis identicos
- excecoes documentadas em comentario: planos (mapper custom + fallback async de titulo duplicado), apoiadores/[id] (flatten p/ controles message-only), convite login (duas fases + redirect)
- pin unitario do wrapper: campaignFormActions.unit.spec.ts (spread, boundary, zod, safe/generic, echo)
2026-07-25 15:33:19 -03:00
Francisco Ignácio Fontoura Solla
e357c3cc19 Pass 2 W4f: varredura de copy Praca -> Municipio
- ~70 strings visiveis + labels de admin (headers de coluna, APIErrors, empty states, dashboard, mapa, formularios, filtros) com concordancia de genero corrigida
- apoiadores desambiguado: cidade do contato vira 'Cidade'; a unidade operacional assume 'Municipio' (filtro, coluna, ficha)
- comentarios de codigo atualizados; payload-types regenerado
- fora por decisao D6: textos provisorios de consent do Onda 0 (lote juridico) e migrations congeladas
- e2e do plano de acao acompanha o label ('Municipio *')
2026-07-25 15:12:07 -03:00
Francisco Ignácio Fontoura Solla
11d3fcd24c Pass 2 W4e: tipos de fonte unica
- triplo de cenarios: municipalityMapData usa VoteEstimateScenarioFields (2 grafias inline eliminadas)
- MAX_LEADERSHIP_MUNICIPALITIES: Leadership.ts importa de lib/schemas/leadership (const local removida)
- campaignUserInitials: copia divergente do AvatarStack removida; fonte unica em campaignUserProfile (com fallback defensivo)
- tendencia politica: options do select da collection derivam de politicalTrendStatuses + politicalTrendLabels; ProjectionTrendStatus vira alias de PoliticalTrendStatusValue
- MunicipalityStrategyViewModel nomeado e exportado (consumidores param de indexar NonNullable<...['strategy']>); nota de por que NAO deriva do zod
- DynamicFind documentado como a unica grafia aprovada do cast
- stub<T> nos doubles de teste onde a checagem por propriedade e real (campaignUserPhone, campaignAuth, campaignConsent); deep fakes mantem o cast localizado
2026-07-25 15:05:03 -03:00
Francisco Ignácio Fontoura Solla
0c1127b35a Pass 2 W4a: electionInsights.ts deletado (941 linhas, 71 exports, 2 vivos)
- formatElectionNumber -> src/lib/electionFormat.ts (13 importers); computeVoteTrend + tipos -> src/lib/voteTrend.ts
- ~52 exports sem consumidor deletados (limiares 35/20/10 da era nucleos, gap vs 2022, conversao, mobilizacao, ticket flip/leverage) - decisao D2; formulas no git history; E10 nasce em modulo novo (nota no plano classificacao-territorial-relativa.md)
- specs: voteTrend.unit.spec.ts (pins vivos + pin do defeito E18 de fracao), electionPartySpectrum.unit.spec.ts sem os blocos de ticket; electionInsights.unit.spec.ts deletado
- fix de regressao W2 pega pelo knip: scripts/import-projecao.mjs importava voteEstimate/slug nos paths antigos (knip agora roda sem pipe mascarando exit code)
- knip unused exports: 157 -> 140
2026-07-25 14:57:49 -03:00
Francisco Ignácio Fontoura Solla
c706c62098 Pass 2 W2: components/campaign em subpastas por dominio
106 arquivos flat -> 15 dominios (actionPlan, advisor, auth, dashboard, demand, invite, leadership, map, municipality, organization, stateDeputy, supporter, votePledge, shared, shell) via git mv; imports absolutos reescritos em 98 arquivos; excecao de lint do campaign-logo e paths dos specs de contrato (campaignInviteUi, campaignComponents) atualizados. Sem mudanca de conteudo nos componentes.
2026-07-25 14:52:17 -03:00
Francisco Ignácio Fontoura Solla
cf6ae6c47c Pass 2 W2: correcoes de fronteira lib/utilities e client boundary
- helpers puros movidos utilities->lib: slug, phone, wordStartFilter, voteEstimate (9 inversoes lib->utilities zeradas)
- chaves de consent em src/lib/campaignConsentKeys.ts (client-safe); campaignInvite/campaignConsent re-exportam; onda0ConsentTexts referencia as constantes
- 'server-only' em 21 loaders Payload/cache (CLI segue com --conditions=react-server)
- ciclo de tipos supporterListFilters <-> supporterUi quebrado (SupporterListFilterInput estrutural; sqlFilters idem)
- votePledgeData dividido: votePledgeViews (puro, client-safe) + votePledgeData (loaders, server-only) - padrao contract-module
- predicados de role client-safe em src/lib/campaignRoles.ts; access/shared deriva deles (fonte unica); nav.ts para de importar o barrel campaignAccess (casts 'as CampaignUser' eliminados)
2026-07-25 14:38:41 -03:00
Francisco Ignácio Fontoura Solla
03bf54dea2 Pass 2 W1-D1: sistema de listas (column defs + CampaignTable) + split do municipalityUi + B16+
- CampaignTable/CampaignTableHead: colunas como dado (id, head island, cell, seams B17 mandatory/defaultVisible); colSpan derivado
- CampaignListFooter: contagem + paginacao compartilhadas
- MunicipalityList reescrita sobre column defs; cards mobile inalterados
- municipalityUi.ts (765/59 exports) dividido: municipalityListUrl / municipalityListFilters / municipalityLabels / municipalitySignal; contrato de URL byte-identico (pins verdes)
- B16+ absorvido: useOptimistic no HeaderFilter (onNavigate dentro da transition), hrefs de opcao via serializador canonico confiavel (1 serializacao por opcao, pin de byte-igualdade), facet de municipio como slugs rotulados no cliente (-20KB), facets no mesmo Promise.all (seed por promise), catalogo DRY, clearMunicipalityListFilters
- timeout explicito no int test de facets (2 bundles full-scope sob suite 47-way)
2026-07-25 14:13:04 -03:00
Francisco Ignácio Fontoura Solla
dc9a39f800 Pass 2 W0: pina contratos de URL e loaders das listas + truth-up TESTING/TECH-DEBT
- unit: campaignListUrl (canonicalize/clamp/redirect), supporterUi, actionPlanUi (matriz tab->where), 5 parsers de entidade
- int: campaignEntityListData smokes (escopo por role, busca por marker, shape de paginacao)
- fixtures: suporte a stateDeputy (create + cleanup)
- docs: contagens reais (45/47/7), Praca ~96 hits, conflito C8 F4 resolvido (10 ladders restantes -> W4d)
2026-07-25 13:30:39 -03:00
Francisco Ignácio Fontoura Solla
8430bd0a9f B16: corrige filtros que se anulavam e registra os débitos pós-simplify
A revisão paralela do /simplify achou três defeitos funcionais na entrega
dos filtros no header, todos corrigidos aqui:

- as facetas omitiam só o param multi, então "Prioritária" escondia os
  municípios não-prioritários do popover onde o próprio check mora e
  "Sem assessor" esvaziava o popover de Assessores; cada faceta passa a
  omitir o grupo de params do seu popover (int test cobre os dois casos);
- "todas as tendências" tinha duas codificações — as três marcadas
  deixavam trend= inútil na URL com o funil se declarando inativo; o
  parse agora canonicaliza o conjunto completo para ausente;
- o filtro de Território havia sumido do mobile ao virar multi.

Também devolve o "Limpar" aos popovers curtos (estava preso ao limiar da
busca) e registra o que sobrou como B16+ (escala/DRY), com o
CampaignListEmptyState compartilhado e o firstValue duplicado absorvidos
no C8, e a fragilidade do build sem o global metadata no site público.
2026-07-25 03:44:25 -03:00
Francisco Ignácio Fontoura Solla
96bf362421 B16: Filtros no header da lista de municípios
Popover de filtro por coluna no desktop (multi-seleção OR em município,
território, assessores e tendência; busca acento-insensível e "Limpar"),
mantendo os selects empilhados no mobile. Header sticky com a página como
scroller e empty state que substitui só as linhas de conteúdo, preservando
overview e filtros. Remove a coluna "Assessoria": a ordenação foi para
"Assessores" e o com/sem assessor virou opção do mesmo popover.
2026-07-25 03:13:28 -03:00
Francisco Ignácio Fontoura Solla
01e52834e2 B19: Gerenciar assessores (/campanha/assessores)
Lista+detalhe única de edição para coordinator/candidate: nome, e-mail e
celular com auto-save debounced (edit-where-you-see-it); carteira de
municípios por chips com agregação de território de identidade,
adição/remoção com UI otimista; criação inline no topo da tabela; ação de
WhatsApp por assessor; reset de senha via e-mail (bloqueado para
placeholders @planilha.invalid). Alinha access de update/phone/atribuição
de município a isCampaignUnrestricted (coordinator + candidate).

Sem migration. Testes int (campaignAdvisorManagement) + unit (schema e
portfólio de municípios) cobrindo access, conflito de e-mail único, cap de
assessores por município e agregação por território/ZE.
2026-07-25 02:40:47 -03:00
Francisco Ignácio Fontoura Solla
ef8dae7fb2 Fix stale computeGoalCoverage call left by main's E18 test file
The rebase auto-merged tests/unit/goalCoverage.unit.spec.ts against
origin/main without a conflict, but one assertion in the merged file
still called computeGoalCoverage with a raw number for the suggested
goal, from before this branch's Fase 0 (SuggestedGoalByScenario). Wrap
it in the file's own suggested() helper, matching every other call
site in the file. Caught by the pre-push tsc gate.
2026-07-25 02:30:09 -03:00