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.
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).
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).
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
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.
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).
- 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
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
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.
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.
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.
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.
- 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
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.
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
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.
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
- 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
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.
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
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
- 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
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
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.
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.
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).
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.
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
- 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>
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.
- 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>
- 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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
- 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>
- 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>
- 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>
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>
- 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>
- 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>
- 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>
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.
Ensure campaignPageChrome targets campaign-page-chrome-title after rebase,
and remove useMunicipalityEstimateScenario orphaned by B129 overview removal.
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.
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>
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>
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>
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>
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>
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>
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>
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>
- 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
- 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>
- 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>
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>
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>
- 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>
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>
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>
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>
- 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>
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).
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).
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.
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.
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.
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>
- 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
- 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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.
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.
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>
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>
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).
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.
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.
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.
- 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.
- 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).
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).
* 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>
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>
- 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>
Nova Soure vs Nova Canaã compartilham o primeiro token; distinctWordStartQuery
escolhe palavra que casa só com o alvo e evita falso positivo no assessor.
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.
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
* 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.
* 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>
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
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
* 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
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.
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.
- 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
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.
Two-step UX-1 flow on the B59 chassis with URL contract ?signalType= and optional ?entryAction= for embedded skip; reuses createMunicipalityListSignalFormAction.
Unify violation highlights with getWizardVoteViolation, share the expected-votes save error constant, and drop dead parseWizardVoteDraft plus unused municipalitySlug prop.
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.
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.
Staff can adjust municipality expected votes through média→pessimista→otimista steps with shortcuts, coherence warnings, and a single POST on completion.
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.
- 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
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.
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.
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.
Staff get six launchers with uncovered-municipalities linking to the sem_assessor list; leaders get two with contacts wired to the shared route constant.
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.
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.
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.
Wire CampaignListPendingBoundary/Results on /campanha/atividades/giros so TI navigation matches list pending UX; pin contract in tourRegionPending unit spec.
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.
Staff sees a Flag + tooltip on the name column and mobile card instead of the destructive “Prioritária” chip, with copy centralized in municipalityLabels.