- restaura o embed do YouTube como superfície opt-in no SpeechDetailPlayer
(a Câmara segue a entrada padrão; o embed nunca é a porta automática, guardrail C178)
- controle simétrico "Assistir no YouTube"/"Assistir na Câmara" no bloco de saída,
com o ponto da fala preservado na troca (pendingSeekRef/youtubeStart)
- SPEECH_VOD_PLAYER_POLICY passa a pollAttempts: 2 / pollDelayMs: 3000 — espera
bounded dentro do clique em vez de tratar o primeiro GERANDO como falha
- unit do resolver (pin + GERANDO→PRONTO) e do player (ida-e-volta + ponto);
pin SSR do e2e mantendo o embed fora do HTML inicial
Closes#1103
Lista + detalhe em /campanha/comunicacao/acervo/cortes: busca por título/descrição,\norigem da fala, player do MP4, edição de título/descrição e kill switch de\npublicar/despublicar no mesmo /corte/<id>. Gate speechCatalog fail-closed para\nadvisor/leader. Sem collection/migration nova.
Seleção do C166 ganha "Cortar vídeo": título/descrição por IA (editáveis, com fallback determinístico), job assíncrono com ffmpeg por re-encode exato e polling dos quatro passos, e página pública unlisted /corte/<id> (noindex, OG, crédito CC BY) servindo o mesmo MP4. Collection speechCut com access canReadSpeechCut (advisor/leader negados) e kill switch por status; ffmpeg entra só no runner da imagem com FFMPEG_PATH para teste.
Papel communicator com nav própria e gate speechCatalog; acervo em
/campanha/comunicacao/acervo com busca normalizada (destaque no trecho),
filtros de ano/tema/alcance/fase/município/duração em URL canônica e
paginação por discurso via speech.searchText (GIN trgm + backfill).
Detalhe com player nativo no trecho, transcrição clicável, baixar/abrir
fonte e fallback sem VOD. Resolve o débito do C153 (Sollinha fora da
vertical) e corrige o parser de keywords oficiais no dono.
Closes#956
Collections speech/speechSegment no grupo Comunicação, papel communicator
(leitura restrita) e índice trigram normalizado para a busca por segmento.
Import idempotente da Câmara (discurso→evento→trecho→VOD→ASR) com facetas
gazetteer+LLM, proveniência e preservação de curadoria manual; parser de
quem presidia; reusa a lógica pura do piloto C152 via scripts/lib.
Related #955
Adiciona o checkbox publicEvent ("Evento público", default false, indexado)
à collection activity para registrar a intenção de expor o compromisso na
futura agenda pública do site — sem consumo agora e sem mudar agenda, lista,
detalhe ou o espelho do Google.
- Activity.ts: campo checkbox + snapshot do guard de liderança
- zod/formData/view model do form + ActivityOverlay (ao lado de "Todo o dia")
- migration add_activity_public_event + types regenerados
- unit (overlay), int (parse/persistência/reabertura) e e2e (roundtrip)
O dispatch manual do C139 montava new FormData(form) sem o submitter;
os botões de transição (name=status value=aprovada|rejeitada|…) nunca
viajavam e transitionDemandFormAction rejeitava toda decisão por status
vazio. submitterFrom (cast honesto nativeEvent as SubmitEvent) + segundo
arg do construtor nos handlers de transição e custo; C139 intacto.
Teste unit novo pina o glue FormData→status (SubmitEvent real, não
fireEvent.submit). E2e 'advisor opens a demand and decides it' verde
local (isolado e em runs de arquivo inteiro).
- useMemo(() => fn, []) → useCallback (idiom do codebase; shim sem props no de municípios, saveErrorMessage no de state-deputies).
- ENDPOINT/GENERIC_ERROR → STATE_DEPUTIES_ENDPOINT / MUNICIPALITIES_ENDPOINT + SAVE_ERROR_MESSAGE (espelho de MunicipalityListAdvisorsControl).
- Prop opcional saveErrorMessage no wrapper de state-deputies: o call site B36 (fromStateDeputy) volta a falar em 'lideranças' no caminho de erro, como a form action removida.
- impl plan: LeadershipListStateDeputiesResponse (não ToggleResponse), useCallback, saveErrorMessage.
F2: POST /campanha/liderancas/state-deputies substitui
setLeadershipStateDeputyMembershipFormAction (chips de /campanha/liderancas B31
e /campanha/dobradinhas B36 — mesma action record).
F3: POST /campanha/liderancas/municipalities substitui
setLeadershipMunicipalitiesFormAction (B34).
Wrappers 'use client' LeadershipStateDeputiesColumnCell /
LeadershipMunicipalitiesColumnCell mantêm o contrato (state, formData) das
shared cells (intocadas); rotas reusam os schemas exportados + allowlists
floor/cap/scope; revalidate herdado das actions record (contrato B34/B31).
F1 (sinal de municípios) não existe mais: C87 o substituiu por form
multi-campo, que por regra B155 permanece em server action — registrado
como já resolvido no plano de intenção.
Guards: whitelist sharedSheetHostConventions (+2 wrappers), prewarm e2e
(+2 rotas), B34 aponta para a rota nova. Sem migration/UI (Impeccable A).
Closes#368
Fila encadeada por instância no MunicipalityRelationEditor: delta otimista
imediato + transporte serializado (próximo elo só dispara quando o anterior
assenta), falha reverte só o delta do próprio elo e a fila segue, busy
derivado da fila (nunca preso), drain aplica o último selectedIDs confirmado
só quando a resposta final o traz (JSON) — sem ele (dobradinhas) confia no
RSC. Substitui requestSeqRef/latestConfirmedRef-seq por fila; tempID de
create em contador próprio. Contratos dos wrappers e call sites intactos.
Fix manual dispatch em 18 formulários de /campanha que usavam <form action={submitAction}>: campos não controlados voltavam ao defaultValue após qualquer action assentada (sucesso ou erro) do React 19.
- WizardStepFormChrome alterado uma vez (cobre 3 wizard steps)
- 15 forms recebem dispatch manual arquivo a arquivo
- AdvisorPasswordResetButton (2 micro-forms sem campo digitável) ficou como está
- Unit spec novo campaignWizardStepFormChrome.unit.spec.tsx
- CHANGELOG-AGENTS.md cleaned (98 duplicates removed)
Also fixes:
- campaignPageActor writable gate only blocks advisors (was blocking coordinators too)
- campaignLeaderships e2e stability (waitForLoadState before attribute assertion)
Dois bugs do C142 que o e2e full do verify do deploy pegou (o PR rodou só o blast radius):
- requireCampaignPageActor gate 'writable' checava advisorEditingAccess(user)==='none', que retorna 'none' para QUALQUER papel não-advisor — barrava coordinator/candidate de páginas de escrita (municipios/[slug]/editar redirecionava o coordinator pro detalhe).
- CampaignQuickActionsFab não renderizava data-slot='campaign-quick-actions-fab', o locator do e2e campaignPermissionProfile.
Regressões pino: campaignPageActorGate.unit.spec.ts (5 casos) + assert de data-slot no spec do FAB.
Verificado: tsc/lint/format/cycles, unit 2435, int 819, e2e local dos specs afetados (os 3 casos que falhavam no deploy passam).
DemandResponsibleMultiSelect (C143), ResponsibleMultiSelect (C90) e
AsyncSearchCombobox trocam o efeito duplicado (requestId/setTimeout 250ms/
loading/failed) pelo hook compartilhado src/hooks/useAsyncSearchOptions.ts,
com isQueryReady opcional preservando a semântica de query vazia de cada
consumidor. Comportamento preservado; unit novo do hook (renderHook +
fake timers); sem schema/server/migration.
- campaignDemand.responsibles (hasMany → campaignUser, staff elegível) com
hook beforeValidate que descarta ids não-staff (fail-closed)
- canRead/UpdateCampaignDemand por membership: assessor só vê demandas onde
é responsável; unrestricted/admin sempre; leader nunca (fim do escopo
municipal de demandas)
- criador entra automaticamente como responsável no hook de criação (form,
wizard, rascunhos C90); migration com backfill created_by_id → responsável
- UI: picker multi-select na criação (nova + wizard) e card de gestão no
detalhe com save full-replace + advisory lock; sugestões = assessores do
município; rótulo 'Visível para: Responsáveis · Candidato · Coordenador'
- int tests reescritos para a regra nova + 5 casos novos; e2e novo de
visibilidade (lista/URL/gestão); fixture e2e com createCampaignDemand
- impl plan + changelog; importMap regenerado com S3_* dummy (OPS69)
Create/edit sheets (ContactFormSheet + ContactFormFields) e cards mobile
(ContactMobileCard/ContactMobileList), FAB de criar (ContactCreateFab,
z-40 sobre o bottom nav) e a ladder updateContactFullFormAction.
- Form sheets com campos controlados + dispatch manual: React 19 reseta
campos nao controlados apos qualquer action assentada, apagando o que
o usuario digitou em erro de validacao/conflito de nome; startTransition
mantem isPending correto e o state.message vira banner role=alert.
- state de acao da sessao anterior e descartado ao reabrir o sheet
(sessionId), evitando banner/conflito fantasma.
- CreateRow usa o mesmo dispatch manual (mesma classe de bug).
- updateContactFull: escrita atomica da ficha em transacao com gates de
escopo e invariante de nome (decisao F do plano).
Fase 2 do plano aprovado. Três módulos em src/utilities/contacts/:
- contactListUrl: contrato de params (q|gender|state|city|ausencia|vinculo|sort|dir|page),
canonicalize, hrefs de sort e where de fonte com 'phones.value' like (ILIKE verificado
no Postgres local).
- contactListData: loader com UM find em contact (user threadado, overrideAccess false),
vínculos (liderancas/dobradinhas/assessores/equipe) como facets sobre o conjunto
legível, filtro/sort/paginação em memória, nulos no fim (precedente B15).
- contactNameInvariant: lock contact-name:{normalized} + prefilter ILIKE + veredito
exato em memória; mensagem de conflito pt-BR.
38 testes unit novos; format + tsc limpos.