- 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
O `pnpm knip` imprimia `ERROR: Error loading src/payload.config.ts
(...server-only...)` e saía 0 — gate verde com o ERROR silencioso, no CI
(`ci-pr.yml`/`deploy.yml`) e local. O plugin embutido do Payload carregava
`src/payload.config.ts` via jiti sob as condições Node padrão e o import
transitivo de `server-only` lançava (só o `react-server` traz o `empty.js`);
o `paths.server-only` do `knip.json` alimenta apenas o resolver estático, não
o carregamento jiti (e o `tsconfig` alias, que funcionaria no load, foi
rejeitado por re-apontar a guarda server/client — classe OPS69).
Fix: `"payload": false` e as contribuições do plugin explicitadas no
`knip.json` (`!src/payload-types.ts` load-bearing; `!src/migrations/**`
paridade), removendo o bloco `paths` inerte. Provado: `pnpm knip` sai 0 sem o
ERROR, um export morto plantado é detectado, e a corrida de controle com
`server-only` neutralizado dá findings idênticos.
Plano: docs/plans/ops-knip-importmap-artifact-impl.md
Corrige o fundo tintado (gradiente do body herdado de :root) com um
layout de segmento `data-theme="editorial"` + scroll interno, espelhando
`[type]/layout.tsx`, e porta o gate aprovado: vídeo herói, título à
esquerda, meta em relógio, prosa medida, kit WhatsApp-first responsivo,
secundárias em grade no mobile e estado indisponível redesenhado.
Extrai os botões do share kit no próprio dono (acervo/biblioteca
inalterados) e adiciona o guard e2e `data-theme="editorial"`.
Fecha o resíduo da classe S6 da OPS119+ no resto de scripts/. F1: SCRIPTS_SPEC_PINNED (42 módulos scripts/** alcançáveis de specs unit+int) espalhado em HIGH_RISK_EXACT, sincronizado por invariante de igualdade que anda o grafo de imports dos specs (missing = lib nova pinada; stale = rename). F2: isCodePath cobre scripts/ (classifyStaticScope = code). Sem migration/access/Consent/UI. gate:fast verde.
Planos de intenção + designs hi-fi do gate para o lote pós-teste do dono:
- C178 #1081 player do YouTube sem parede de signin (defect, P1)
- C172 #1082 player/link no ponto certo da fala (defect, P1)
- C173 #1083 pré-visualizar o trecho selecionado (P2)
- C174 #1084 encontrar cortes (nav, fala, busca) (P2)
- C175 #1085 preview do trecho na listagem (P2)
- C176 #1086 página pública do corte — design (P2)
- C177 #1087 'Abrir fonte' só com fonte oficial (chore, P3)
- OPS122 #1088 launch do worktree honra modelo e --auto (defect, P2)
Related #1081, #1082, #1083, #1084, #1085, #1086, #1087, #1088
O painel `pnpm issues:tui` volta a enxergar o design UI depois do rename do
OPS114: `siblingPlanPaths` deriva `-ui-design.html` (vigente) e
`-ui-draft.html` (legado) e o novo `resolveUiDraft(planPath, exists)` — puro,
com a existência injetada pelo CLI — é dono da precedência (design vence,
draft é fallback, nenhum → design com `exists:false`). O `readPlans` sonda
design→draft no disco e o caminho REAL flui por `plansByNumber`/`buildIssueRow`
até o view model; o contrato `--json` segue `version: 1` com o shape
`{ path, exists }` e a chave histórica `uiDraft`. As superfícies do painel
trocam "rascunho UI" por "design UI".
Retrocompat é só de leitura — o acervo legado (30 `*-ui-draft.html`) não é
tocado. Unit dos dois specs cobre design-only, draft-only, ambos, nenhum, o
caminho real, body linkando o impl e o shape do JSON. Sem migration.
`worktree plan` sem argumento cria a sessão persistente sem driver (contrato do
`new`); com bag auto-submete uma única `/plan-issue <bag>` e usa o mesmo texto
como rótulo do branch, espelhando o `fix`.
- purposeInvocation (lib/agent-session): ramo plan reusa a sanitização do fix e
devolve null quando o bag fica vazio (driverless por desenho).
- opencodeLaunchDirective emite --argument para fix||plan; cmdPlan repassa
argument: bag; cmdStart isenta plan do aviso "sem comando".
- next/new/fix, --stay e o comando /worktree intactos.
- Specs repinados; textos vivos e changelog atualizados. Débito de risk map das
libs + pin do wiring registrado em #1071.
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.
O events.watch serializa o expiration (int64 epoch ms) como string JSON;
o client tipava number|null e o motor lancava RangeError: Invalid time
value no new Date(...).toISOString(), gravando pushChannelError sem
persistir o canal.
- parseGoogleWatchExpiration (borda do client) normaliza string|number
para number|null com teto do range do Date; malformado cai no fallback
de PUSH_CHANNEL_TTL_SECONDS, nunca lanca.
- pushChannelError ganha a causa do fetch (error.cause) preservada no
teto de 500 chars.
- Unit: stub de transporte modela /watch (string real) + matriz do
parser; int: caminho number->ISO, fallback null->now+TTL e causa.
Closes#1009
Workflow separado auto-unblock.yml (workflow_run no Deploy, push/main + job verify vermelho pela jobs API), launcher com flock próprio + Issue token auto-unblock como single-flight, e wrapper destacado que provisiona worktree fix/* via worktree.mjs fix --headless, aplica guard de banco local teqo_wt* fail-closed e roda opencode run --command bug-fix (cap 4h). deploy.yml/verify intocados; sem auto-retry e produção segue com approval humano.
A varredura de produção (2026-09-14) achou 33 falas em selCodColecaoCsv=J e 1
com o parâmetro vazio: o endpoint devolve 'Documento não encontrado' e a mesma
data/página resolve na coleção D que a busca atual da Câmara gera. Aliases
J/vazio → D, puros e unit-testados; DCN e demais códigos ficam como estão.
- puros: classify/parse da URL legada (dc_20b/dc_20, montaPdf), build do PDF
direto com #page, cache key por publicação, plano de reparo e fallback
- camaraFetch: resolveOfficialPdfUrl segue o redirect via .leg.br (fail-closed)
e probeLink generalizado (Range + contentType)
- import: normaliza officialTextUrl no write path (cache <out>/diario por
publicação; falha preserva PDF direto anterior, senão null)
- novo modo --repair-links [--dry-run] com guard CAMARA_IMPORT_CONFIRM fora de
alvo local, relatório de cobertura e idempotência
Registra os planos aprovados no gate de 2026-09-14: tool do Sollinha para sugerir trechos do acervo de falas (C158) e chat escopado ao acervo para a assessoria de comunicação (C159, depende de C158).
Related #982
Related #983
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
Chave sufixada por hash de conteúdo para falas distintas que compartilham
data/tipo/fase (caso real 2026-06-17T17:16); keywords oficiais deixam de
alimentar o gazetteer de lugares; nomes ambíguos (Saúde, Wagner, Santana...)
exigem contexto de lugar; teste de nav do communicator; plano com prova viva
e changelog.
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