Commit graph

435 commits

Author SHA1 Message Date
Francisco Solla
e46ed57e2c feat(C244): faceta pessoa do álbum lê o reconhecimento facial 2026-10-02 09:35:43 -03:00
Francisco Solla
625ab5c10e feat(C244): collection curada faceFigure, migração e matcher puro 2026-10-02 09:35:43 -03:00
Francisco Solla
4ac13dc351 feat(S45): cards verticais no catálogo, na home e na página da peça 2026-10-01 19:17:06 -03:00
Francisco Solla
0a8b0774d3 feat(S44): home divulga a Plenária da Vitória (agenda pré-live, embed ao vivo e kill switch)
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
2026-10-01 17:47:30 -03:00
Francisco Solla
cd4942d9e2 feat(C242/C243): abre a busca por selfie a qualquer visitante (escopo B) + seção na homepage
O escopo A/C do gate (PR #1370) é superado por decisão do dono (2026-10-01, aval jurídico declarado): o índice vira anônimo (archivePhotoFace, foto + descritor 128-d, sem nome/vínculo), o enrollment por pessoa morre (faceSubject/faces:enroll removidos) e qualquer visitante encontra as próprias fotos aprovadas.

- faces:index persiste os descritores por foto (transação substitui as linhas + marker = modelo); desaprovar/remover/deletar foto apaga os descritores (hooks + FK cascade)
- POST /api/fotos/selfie casa o vetor da consulta contra o índice em memória (TTL 60 s) e devolve só a interseção com o read aprovado do C233, sem score/nome de terceiro; leave-index apaga os descritores que casarem
- Consentimentos seguem fail-closed: busca-selfie-fotos (consulta) e busca-selfie-indice (aviso público do índice), textos versionados em scripts/lib/faceConsentTexts.mjs e provisionados por pnpm seed:face-consents
- pnpm archive:publish aprova o acervo em lote (draft -> approved; removed nunca) com guardas e recibo
- C243: seção condicional SelfieSearchHomeSection (port do design hi-fi, cenas 390/1280) entre a Central e a newsletter; sem engine/captura na home e sem CTA morto quando fechada
- docs: planos C242/C243, AGENTS-public, runbook §C242, changelog

Provas: gate:fast 457 arquivos/5.055 testes; int full 109/1.075; e2e frontendFotosSelfie 9/9; knip verde.
2026-10-01 12:54:47 -03:00
Francisco Solla
ca843fb4bb fix(C230): fallback da catalogação dá título semântico, não placeholder
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
Quando a sugestão por IA falhava (timeout de 8s) ou a peça não tinha
texto, o fallback preservava o título provisório `Instagram · <código>`
para sempre. Agora isContentPieceLinkTitle (dono em src/lib) identifica o
placeholder e o fallback gera título determinístico (tipo + cidade +
temas); o caminho sem texto faz o mesmo em vez de deixar o placeholder,
a sugestão da IA que ecoa o placeholder é descartada, e título editado
pela assessoria continua intocado.
2026-10-01 02:29:19 -03:00
Francisco Solla
862da74fbe feat(S42): seção da Central compartilha a peça e abre /conteudos já filtrada
A seção S39 era só vitrine: quem via a peça do seu território tinha de ir
até o catálogo para compartilhar ou recortar. O card agora abre a MESMA
folha do S27 (mensagem de voto por tipo, contador C213, sem sair da home) e
as tags com faceta honesta — mais a fileira Tipo | Cidade | Região — levam
a /conteudos já filtrada pelo construtor canônico; nada filtra a amostra
local e as tags sem faceta (Mais recente, origem) continuam estáticas.

As facetas da fileira são derivadas no server (contentPieceCatalogFacets,
mesmo vocabulário do catálogo) e o lean ContentPieceHomeItem ganha só
sourceUrl, com a folha S27 estreitada ao que lê; classes novas são
home-scoped — CONTENT_PIECE_TAG e /conteudos ficam intactos. Sem migration,
Consent ou analytics novos.

Closes #1391
2026-09-30 01:09:07 -03:00
Francisco Solla
3201b7a93f feat(C237): vetor da fala inclui o sumário/keywords oficiais
A Fase 5 pós-deploy do C229 mediu o corpus real e o aceite 'impeachment'
falhava: a 222 (votação do impeachment de Dilma, 17/04/2016) ficava em #841
porque o índice só embutia o ASR — e a fala nunca pronuncia 'impeachment';
a relevância dela mora no sumário/keywords oficiais da Câmara.

O plano do índice passa a carregar metadataText (sumário — ou título no web
— + keywords, incluído no hash de idempotência) e o vetor da fala vira
normalize(média das unidades + 2·metadata), calibrado no corpus real
(w=2: 222 em #8; w=1: #25). A metadata é vetorizada no mesmo lote do
provedor mas não vira unidade armazenada nem evidência; sem migration.

Related #1395
2026-09-29 22:53:41 -03:00
Francisco Solla
10327a7451 feat(C236): publicar, despublicar e apagar em lote na Central de Conteúdos
A lista /campanha/comunicacao/conteudos ganha um modo de seleção opt-in
que envolve os dois corpos (tabela e card list) com um estado client
compartilhado, chaveado pela URL canônica (a seleção morre em
página/filtro e nunca cresce para além da página visível). Com seleção
ativa, a barra flutuante aplica o mesmo veredito unitário à seleção:
Publicar/Despublicar agem direto com recibo honesto; Apagar sempre
confirma no CampaignDeleteDialog reusado (novo caminho onConfirm
aditivo), avisando os links públicos só quando a seleção carrega
publicadas.

O transporte é uma rota única (POST .../conteudos/lote,
campaignJsonMutationRoute, teto de 50 ids) que itera server-side as ações
donas: cada peça mantém a própria transação, o gate fresco e a limpeza
best-effort de mídia; a falha de uma nunca reverte as demais e o recibo
separa afetadas x falhas (2 de 3 peças apagadas. 1 falhou.).

Sem migration, sem Consent novo, sem rota pública (S27) e sem mudar a
semântica do C222. Cobertura: int do lote (papéis, dedupe, mídia, evento,
tag, falha parcial, schema), unit do builder/barra/seleção e e2e HTTP.

Design tier: DEGRADED (artefato de origem sem certificação frontier) —
crítica do designer aplicada no escopo do item; entrega bloqueada para
sign-off humano antes do PR. Related #1388
2026-09-29 22:26:26 -03:00
Francisco Solla
f1bc634385 chore(C235): remove exports órfãos apontados pelo knip
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
2026-09-29 13:23:01 -03:00
Francisco Solla
8608aeb317 feat(C235): importa qualquer janela de publicações do perfil na Central
A importação do perfil oficial deixa a janela fixa de 12 mídias e ganha o recorte por período (desde; até opcional), com prévia honesta (novas · já estavam · total) e confirmação antes de criar; "Recentes" segue o default e o clique único. O planner puro de janela/dedupe passa ao dono em src/lib (CLI re-exporta) e a varredura usa o teto de páginas derivado de maxResults, com early-stop por data e corte denunciado (truncated). Sem migration/Consent; tudo Rascunho; fail-closed de credencial intacto.

Design tier: DEGRADED (central-conteudos-importar-janela-ui-design) — port classe-a-classes das cenas 01–08 com sign-off humano explícito (2026-09-29); a crítica de paridade do designer no tier primário não pôde rodar por quota.
2026-09-29 13:23:01 -03:00
Francisco Solla
ad2b165e5e fix(C231): EXIF do acervo volta ao mapper e ganha backfill --refresh-metadata
A API do Flickr responde raw/clean como { _content } e o mapper só aceitava
string — as 6.492 fotos do backfill gravaram exif: []. archivePhotoExifEntries
desembrulha os dois shapes (contentScalar também substitui o unwrap ad-hoc do
description) e o CLI ganha o modo --refresh-metadata, que re-busca o EXIF das
linhas existentes e atualiza só o que mudou: write-only no campo exif, sem S3,
guardas de confirmação/TEQO_ENV e recibo atualizadas/inalteradas/falhas.
Runbook §C231 documenta a operação. Closes #1383.
2026-09-29 09:11:49 -03:00
Francisco Solla
722cd6cf94 fix(C199): upload de gravações fatiado em 16 MiB — escapa do 413 do edge
O diálogo "Enviar gravação" mandava o arquivo inteiro num único XHR e a
produção vive atrás do Cloudflare Tunnel, cujo edge responde 413 para
qualquer request body acima de ~100 MB ANTES do app ver o corpo (probe
read-only: POST de 150 MB cortado após ~3,2 MB). Um .mp4 de 6 GB de
plenária nunca chegava à rota — o C199-large havia removido só o teto do
app, não o do proxy.

O upload agora é fatiado ponta a ponta: o start bodyless cria a row e a
sessão em disco (`recording-upload-<id>` com estado + arquivo anexado), o
diálogo envia partes de 16 MiB em ordem estrita com progresso agregado e
o último byte do tamanho declarado finaliza a mídia privada e o job. Cada
parte faz heartbeat do `updatedAt` contra o reaper de 1 h, o start faz
preflight de disco (statfs + margem), falha de parte aborta a sessão e o
delete do usuário/reaper limpam o diretório. Sem migration e sem schema.

Regressão: protocolo int nas rotas start/chunk/abort (multi-parte, fora
de ordem, tamanho excedido, seam multipart, falha S3, reaper), guarda
unit do orçamento do edge, guardas e2e das rotas. Post-mortem e entrada
de changelog em docs/.
2026-09-28 19:36:12 -03:00
Francisco Solla
9e9864f420 feat(C234): busca por selfie no álbum público — índice A/C consentido, selfie on-device
- /fotos/encontre + POST /api/fotos/selfie: só o vetor 128-d, sem imagem, sem score e
  sem nomes de terceiros; fail-closed pela flag photoAlbum.selfieSearchEnabled (default
  false) e pelos Consents por chave estável (busca-selfie-fotos/busca-selfie-indice, não
  seedados — a superfície nasce fechada até o aval jurídico/DPIA).
- Índice A/C do gate (PR #1370): só quem aderiu é indexado; descriptors do acervo são
  transitórios. Collection faceSubject (admin-only, sem vínculo com Contact), vínculos
  matchedPhotos e marker faces.checkedKey no archivePhoto (modelo + enrolledAt).
- Engine @vladmandic/face-api nos dois lados: browser lazy com modelos same-origin
  (copy-face-vision-assets no prebuild/predev/Docker) e CLI node-wasm sem tfjs-node.
  CLIs ops-only faces:enroll / faces:index --apply|--verify com guardas C231/C232.
- Opt-out self-service por reconhecimento (leave-index) + canal de remoção do C233;
  rate limit próprio (namespace selfie:<hash>) e bounded body extraído do beacon.
- UI portada classe-a-classe do design estendido (paridade certificada, tier primário);
  e2e frontendFotosSelfie (manifest+curado+projeto, engine stubada) e canário do engine
  real; runbook §C234, contrato em AGENTS-public.md e changelog.
- Migration aditiva add_face_subject (SQL revisado); hard-stops aprovados na sessão.
2026-09-27 20:04:41 -03:00
Francisco Solla
b2d6483532 feat(C233): álbum público de fotos com busca por data, local, atividade e pessoa pública 2026-09-26 23:54:50 -03:00
Francisco Solla
57b0d168d4 feat(C199-large): gravações do acervo sem o teto de 4 GB
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
O diálogo, a rota e o stream deixam de recusar arquivos acima de 4 GiB; o
nome do arquivo e o `formatRecordingFileSize` continuam iguais. Acima de
2 GiB a mídia não passa mais pelo `fs.readFile` do Payload local API: a row
nasce de um placeholder de 0 byte e a mesma key é sobrescrita por upload
multipart direto no S3 (padrão C225-large, extraído para
`utilities/privateMedia/privateMediaUpload.ts` e reusado pelo espelho de
falas web, que mantém o teto de 5 GiB via `maxBytes` do chamador). O objeto
é removido se a transação falhar, sem row fantasma, e o upload multipart de
gravações não tem teto (`Infinity`).
2026-09-26 14:40:16 -03:00
Francisco Solla
671af1e21f feat(C232): pré-catalogar o acervo de fotos com IA local e curadoria que vence
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
Collection archivePhoto ganha o grupo catalog (legenda/descrição/cena/texto
visível/temas/pessoas/município/origem/catalogedAt), curatedFields (o que a
assessoria edita nunca é sobrescrito), takenOn e searchText com índice GIN trgm
(migration aditiva add_archive_photo_catalog); hook beforeValidate deriva o
índice e marca curadoria só em edição humana; pipeline transacional baixa os
bytes privados, prepara JPEG <=1024px com sharp e mescla o VLM local com o
gazetteer/catálogo curado (pessoas só do TEXTO, nunca de rosto); CLI
pnpm archive:catalog (plan/--apply/--verify, recibo honesto, idempotente por
catalogedAt) com engine OpenAI-compatible na rede privada atrás de guarda de
host (ARCHIVE_VISION_ALLOW_REMOTE é o escape); client HTTP com retry/timeout e
parser zod fail-closed; resolver de município extraído para owner compartilhado
com o catálogo de peças delegando; unit/int novos e runbook staging-primeiro.
2026-09-26 12:30:34 -03:00
Francisco Solla
3e8429daae feat(C229): busca por sentido no acervo de falas
Troca o motor do modo tema (Câmara + internet) de expansão lexical para
embeddings (Deep Infra, BAAI/bge-m3), com índice próprio (speechEmbedding),
ranking por proximidade em memória, evidência real do trecho mais próximo,
vazio honesto por limiar e degradação para a busca literal. Inclui a
collection oculta + migration, o CLI pnpm acervo:index (--dry-run/--limit/
--source/--force/--probe), o port do design aprovado e a suíte de testes.

Pós-rebase: o snapshot da migration seguinte (20260926_062039_add_archive_photo,
do C231 já na main) foi regenerado para incluir speech_embedding — sem ele o
próximo migrate:create recriaria a tabela.

Closes #1360
2026-09-26 11:59:21 -03:00
Francisco Solla
aba3aa4816 feat(C231): ingerir o acervo de fotos do Flickr no storage privado
Collection privada archivePhoto (flickrId unique+index, metadados do Flickr +
EXIF, leitura pela comunicação, mutação admin, entrada no mapa S3) com migration
aditiva add_archive_photo; ingestão idempotente (transação + upload) e CLI
pnpm flickr:import (plano dry-run por padrão, --apply com intenção/TEQO_ENV/S3,
--verify inventário read-only, recibos JSON em data/flickr/reports). Guard de
alvo extraído de reel-ingest para scripts/lib/cli.mjs; downloadUrlToFile ganha
headers (User-Agent do Flickr).
2026-09-26 05:40:22 -03:00
Francisco Solla
4a07bac596 feat(C230): importar publicações do perfil oficial na Central
Lê a janela recente (12 mídias) de @depjorgesolla pela Graph API oficial e
cria Rascunho só para as novidades pelo pipeline C211/C220 (ou peça-link com
motivo honesto), com progresso real e recibo novas/já estavam/falharam.
Dedupe pela identidade do post (shortcode em qualquer grafia p/reel/tv), sem
migration, compartilhado com o Adicionar por link; fail-closed de credencial
nas duas pontas; nada é publicado.

Closes #1362
2026-09-26 02:06:17 -03:00
Francisco Solla
dbe77f08cc feat(C226): frame estático nos vídeos da Central antes do play
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
Peça de vídeo publicada e com arquivo passa a mostrar um frame estático
leve no card público antes do play, sem nenhum request do arquivo antes
do gesto (`preload="none"` do S27 intacto).

O frame é um derivado privado em `contentMedia` (nome determinístico),
gerado por um dono só — `ensureContentPieceFrame` — chamado pelo job
C211 (que já tem o arquivo em disco: a extração sai de graça) e pela
rota `GET /conteudos/<slug>/frame` em miss, que cura o acervo anterior
sem backfill, fila, collection nova ou migration.

A porta reusa o gate de slug publicado e o proxy de mídia privada do
`/midia`: o kill switch fecha as duas de uma vez. Sem frame possível, o
slot mostra o estado neutro honesto com play e o marcador — nunca
imagem quebrada, nunca retry.

A projeção declara o frame por elegibilidade (vídeo com arquivo), não
pela existência do derivado, então a cura é invisível para o cache da
listagem e não há tag para bustar. O frame também aparece na página da
peça e no card da home (variante compacta de 124px, cena 05).

A passagem de design (tier primário) achou dois bugs que só o browser
mostra: o glyph de imagem quebrada numa requisição que falha, e — ao
esconder a imagem até decodificar — o frame sumindo para sempre no
caminho mais rápido (uma imagem que carrega antes da hidratação não
dispara `load` nem `error` de novo; o `ref` decide os dois desfechos).

Também: o guard de falha de e2e aceita `RegExp` e confere os paths
declarados antes do caso do favicon (todo 404 do browser traz a mesma
mensagem), o manifest de e2e mapeia os módulos novos, e o job C211
passa o arquivo que já tem em disco em vez de rebaixar o vídeo.

Design tier: central-conteudos-preview-frame-video
Closes #1352
2026-09-25 14:56:06 -03:00
Francisco Solla
0b05dfe9a0 fix(C223): aceita falas longas no acervo
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
2026-09-25 02:11:56 -03:00
Francisco Solla
01831446df feat(S41): assina e ancora eventos de agenda do link (Closes #1335) 2026-09-25 01:47:18 -03:00
Francisco Solla
63bd370b6c feat(C222): apagar uma peça na Central de Conteúdos (Closes #1330)
Hard delete por peça (sem lixeira/lote/telemetria) em qualquer estado, com confirmação que nomeia o link público só quando publicado; access próprio da vertical + admin (advisor/leader fail-closed), cascata da mídia pós-commit e bust da tag contentPieces no afterDelete; máquina do diálogo extraída no 3º call site (C183/C199/C222).
2026-09-24 23:49:28 -03:00
Francisco Solla
c9ab467f23 feat(C217): cortes e links compartilhaveis na fonte "Falas na internet" (Closes #1293) 2026-09-24 20:17:54 -03:00
Francisco Solla
b119d203aa feat(S37): liderancas que aparecem nas pecas na Central de Conteudos (Closes #1303)
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
2026-09-24 17:40:57 -03:00
Francisco Solla
d0063d39f3 refactor(C216): aplica achados do simplify (Related #1292) 2026-09-24 17:01:42 -03:00
Francisco Solla
425361d278 feat(C216): terceira fonte do acervo — falas na internet (Closes #1292) 2026-09-24 17:01:42 -03:00
Francisco Solla
13b5a3c1d1 feat(S38): alterna peças e cards no board e certifica o design (Closes #1304) 2026-09-24 16:57:03 -03:00
Francisco Solla
e7055ae33d refactor(S38): reusa o label do dono, fidelidade ao artefato e simplifica o filtro (Related #1304) 2026-09-24 16:57:03 -03:00
Francisco Solla
bf2c7a25c4 feat(S38): cards personalizáveis como itens do catálogo (Closes #1304) 2026-09-24 16:57:03 -03:00
Francisco Solla
02ca466469 feat(S40): arte genérica do Time do estadual e janela vazia sem silhueta (Closes #1316) 2026-09-24 16:23:44 -03:00
Francisco Solla
11de48f726 feat(S39): seção da Central de Conteúdos na home (Closes #1305) 2026-09-24 14:53:36 -03:00
Francisco Solla
38e6742673 feat(C219): paridade de busca e filtros nas gravações do acervo (Closes #1295)
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
Classificação automática com proveniência (job + CLI recordings:classify), facetas ano/tema/alcance/município/duração na URL canônica, busca por tema no corpus próprio, ordenação e a barra de filtros portada do design aprovado sobre os shells compartilhados. Deep-links existentes byte-idênticos; Câmara intocada.
2026-09-24 04:00:06 -03:00
Francisco Solla
8f3118312a fix(C220): link do proprio Instagram baixa e cataloga a peca (Closes #1302) 2026-09-24 03:51:17 -03:00
Francisco Solla
8318fb5fce feat(C215): ingestão de falas da internet no acervo — origem web, espelho privado e catálogo (Closes #1291) 2026-09-24 03:26:10 -03:00
Francisco Solla
2c8b38f2be feat(S34): colinha usa a arte exata com a linha do estadual preenchida (Closes #1298) 2026-09-24 02:20:44 -03:00
Francisco Solla
9069d23d88 feat(S33): ajuste da foto do card do time sem limite (Closes #1297) 2026-09-24 01:38:06 -03:00
Francisco Solla
e8a190005a feat(S32): contadores anônimos de download dos cards na home staff (Closes #1273) 2026-09-23 18:12:42 -03:00
Francisco Solla
8d81883509 feat(S28): busca por tema na Central de Conteúdos pública 2026-09-23 15:35:21 -03:00
Francisco Solla
f47c2645ac feat(C213): contadores anônimos de circulação por peça na Central de Conteúdos (Closes #1258)
Collection contentEvent (hidden, sem hooks, sem PII) + beacon público
POST /api/content-events fail-soft, download contado no servidor na rota
de mídia, agregação em uma query group-by e a coluna "Circulação" na lista
interna + painel na ficha (port classe-a-classe do artefato estendido,
crítica final do designer certificada).
2026-09-23 15:29:43 -03:00
Francisco Solla
48a01de58b fix(S29-FOLLOWUP): .ics do link de anúncio e feed — folding RFC 5545 (75 octets) no owner ical.ts (Closes #1281) 2026-09-23 14:16:25 -03:00
Francisco Solla
2a0dca6243 fix(S29): achados da review — snapshot pós-rebase, manifest e2e e a11y do anúncio
- snapshot drizzle da migration regenerado pós-rebase (inclui as tabelas do
  S27; sem ele o próximo migrate:create recriaria content_piece)
- manifest e2e: CampaignFooter/jingleReads/campaignTime acordam o
  frontendShareLink; dynamicParams inerte removido da rota .ics
- anúncio: dl válido (Local sem dd), aria-live só no status/chip, sem
  hasImage/cast; hook direct deixa o erro de URL falar antes (+ teste int)
- débitos triados no impl plan (folding .ics -> #1281; deferidos com gatilho)

Related #1268
2026-09-23 12:37:00 -03:00
Francisco Solla
b0bac78090 feat(S31): colinha de voto — 6º modelo com o estadual escolhido (Closes #1272) 2026-09-23 12:33:04 -03:00
Francisco Solla
b8051bb7e0 feat(S29): link com destino trocável e página de anúncio da atividade
- shareLink ganha modo (direct|announcement), pool de destinos com "Destino no
  ar" por linha e dados do evento; destination escalar sai via migration com
  backfill lossless (legado vira direct com destino no ar)
- página de anúncio pública portada classe-a-classe, header campaign-site
  extraído (CampaignPageHeader) e ativação/troca sem reload por poll da rota
  /api/share-link/<slug>/live (fail-closed, no-store)
- agenda Google (action=TEMPLATE) + .ics por rota sob o slug; compartilhar o
  link curto (kind event); S19 intacto e 404 uniforme preservado

Related #1268
2026-09-23 11:48:12 -03:00
Francisco Solla
dda55adc27 feat(S27): Central de Conteúdos pública — Peça voto pra Solla 1313
/conteudos + /conteudos/<slug>: catálogo filtrável (Tipo · Cidade · Região ·
Tema · Instituição + termo na URL canônica), play sob demanda (Vídeo/Áudio só
carregam no toque; Foto com preview leve), Baixar e Compartilhar do próprio
card com a mensagem de voto por tipo (wa.me sem destinatário, editável; "A
mídia" usa a folha nativa e cai em download onde o aparelho não suportar) e o
card personalizado como peça sintética do catálogo → /cards.

- leitura pública nova (contentPieceReads) cacheada na tag contentPieces com o
  gate where status=publicado + contentPieceIsPublic; a rota GET
  /conteudos/<slug>/midia reusa buildPrivateMediaResponse (range, disposition,
  private no-store, 404 uniforme) com download jorge-solla-1313-<slug>.<ext> e
  a allowlist inline do dono estendida (webp/gif/avif + áudios das peças)
- contrato público puro do catálogo (filtros/VM/download) + mensagens de voto
  (contentPieceShare); OG com fallback do global; conteudos entra nos slugs
  reservados; kill switch por peça vale na hora (lista, página, rodapé/selo);
  descoberta condicional no CampaignFooter e no header local
- UI portada classe-a-classe do design aprovado (cenas 01–08, com as cenas
  06–08 estendidas no trigger a); sem migration, sem Consent e sem access novo;
  contentMedia segue privada (sem segunda cópia de mídia)
- testes: 2 unit specs novos, 18 casos int (leitura pública + rota de mídia) e
  e2e frontendConteudos (5 casos); manifest e curado atualizados
- follow-up de engenharia: #1276 (docs/plans/escala-dry-pos-s27.md)

Closes #1255
2026-09-23 11:13:57 -03:00
Francisco Solla
e72bfe8f8f fix(S30): TeamCardSubject é tipo interno do renderer 2026-09-23 10:06:17 -03:00
Francisco Solla
6480768926 feat(S30): card Time do estadual — 5º modelo com as 53 dobradinhas (Closes #1271) 2026-09-23 10:06:17 -03:00
Francisco Solla
cb61cf1152 feat(C211): Central de Conteúdos interna — envio em lote, link, catalogação e publicação
Envio em lote (1 request raw-body por arquivo, fan-out ≤2, até 50 arquivos) e
adicionar por link (Instagram/YouTube; extração só pela Graph API da conta
própria, senão peça-link) em /campanha/comunicacao/conteudos. Cada peça tem
estado próprio (Processando/Pronto/Falhou + Reprocessar), transcrição/texto e
catalogação automática revisável; publicar/despublicar preserva arquivo, slug
e publishedAt.

- collections contentPiece + contentMedia (upload privado no S3) e migration
  20260923_032714_add_content_piece (slug único gerado no 1º publish,
  sourceUrl única, searchText com índice trigram)
- pipeline reusa C199 (job after() + reaper + retry condicional, transcrição
  DeepInfra, ffmpeg/chunk), o classificador de facetas do acervo e o
  institutionCatalog; curatedFields impede o job de sobrescrever curadoria
- gate único canReadCommunicationCatalog (advisor/leader negados); delete
  só pelo admin; sem Consent novo
- UI portada do design aprovado (lista desktop + cards mobile, diálogo de
  lote com progresso por arquivo, diálogo de link, ficha editável, kill
  switch); nav/chrome/column-picker/manifest atualizados
- instagramFeed expõe mediaUrl; revalidateTag ganha wrapper seguro fora de
  request (fixtures/scripts)
- testes: 2 unit specs, 1 int spec (16 casos) e 7 e2e na vertical
- follow-up de engenharia: #1267 (docs/plans/escala-dry-pos-c211.md)

Closes #1254
2026-09-23 02:25:15 -03:00
Francisco Solla
bb550cebff feat(S25): player próprio da Rádio 1313 na home — exclusividade, share e fail-soft (Closes #1239) 2026-09-21 13:39:42 -03:00