From ebe1a98e85587eebf7d2dae7c13d5b60737fbb2e Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Thu, 24 Sep 2026 16:34:53 -0300 Subject: [PATCH] =?UTF-8?q?style(C216):=20aplica=20a=20cr=C3=ADtica=20visu?= =?UTF-8?q?al=20do=20designer=20(paridade=20com=20o=20artefato)=20(Related?= =?UTF-8?q?=20#1292)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog/2026-09-24-c216.md | 2 +- docs/plans/acervo-fonte-falas-web-impl.md | 4 ++ .../acervo-fonte-falas-web-ui-design.html | 16 +++---- .../(app)/comunicacao/acervo/page.tsx | 45 ++++++++++--------- .../CampaignTranscriptSegmentButton.tsx | 7 +-- .../campaign/speech/SpeechAcervoFilters.tsx | 42 +++++++++-------- .../campaign/speech/WebSpeechDetailPlayer.tsx | 11 ++++- .../campaign/speech/WebSpeechResultCard.tsx | 7 ++- 8 files changed, 80 insertions(+), 54 deletions(-) diff --git a/docs/changelog/2026-09-24-c216.md b/docs/changelog/2026-09-24-c216.md index 196bdd20..76883f55 100644 --- a/docs/changelog/2026-09-24-c216.md +++ b/docs/changelog/2026-09-24-c216.md @@ -1 +1 @@ -**C216 — Terceira fonte do acervo: "Falas na internet" (2026-09-24):** o alternador do acervo passa a ter três fontes — "Falas da Câmara", "Gravações enviadas" e "Falas na internet" — e a terceira abre sobre o catálogo web do C215 com a paridade de busca e filtros da Câmara: busca textual com destaque, `mode=termo|tema`, ano, tema, alcance, duração, municípios citados, contagem, paginação e ordenação. Contrato sem migration e sem twin: o vocabulário de fonte saiu de `recordingListUrl` para o dono puro `lib/acervoSource` (3 vias, desconhecido→Câmara, fail-closed) e o de ordenação para `lib/acervoListSort` (2º consumidor do vocabulário do C219, com o mapeamento de Payload por domínio); `speechListUrl` ganha `source?: 'internet'` e `sort?` de forma **aditiva** (os bytes canônicos da Câmara ficam intocados, a fase é ignorada na web) e `speechListFilters` deriva `origin: web` do estado, mantendo a fase só na Câmara e o gate de duração (`durationSeconds exists`) nos sorts de duração. Loaders e view models ficam nos donos (`speechPageData` ganha `loadWebSpeechAcervoPageData`/`loadWebSpeechDetailPageData`/`loadWebSpeechTitleForActor` e opções de filtro por origem; `speechViewModels` ganha a família `WebSpeech*`) e o detalhe da Câmara passa a filtrar `origin: camara` — um id web vira 404 honesto, e vice-versa. Rotas novas em `/campanha/comunicacao/acervo/internet/[id]` (+ `/arquivo` e `/capa`), precedente `gravacoes/[id]`: a mídia espelhada privada é servida pelo dono `buildPrivateMediaResponse` com gate `canReadCommunicationCatalog`, range/416/404 silencioso e **nome de download derivado do título** (`webSpeechDownloadFilename` fecha o defer S4 do C215); a capa usa a mesma rota autenticada (nunca `/api/media/file`) e o card sem thumbnail renderiza "SEM CAPA". UI (Impeccable C, porte do design aprovado `docs/plans/acervo-fonte-falas-web-ui-design.html`, estendido pelo `designer` na cena 05 com o player de áudio e os chips de plataforma Instagram/Áudio): `AcervoSourceToggle` vira shell de 3 abas em `shared/` (grid de rótulos curtos no mobile), `AcervoSortSelect` compartilhado (serializador escolhido pelo discriminador de fonte dentro do client component — função nunca cruza a fronteira server→client) e `CampaignTranscriptSegmentButton` movido para `shared/` com tipo estrutural; `SpeechAcervoFilters` ganha props de copy (defaults exatos da Câmara, sem Fase na web), `WebSpeechResultCard/List` portam a cena 01 e `WebSpeechDetailPlayer` a 02/05 (vídeo ou áudio nativo no mesmo palco com o badge "Arquivo espelhado · privado", "Baixar", "Abrir na origem", box de origem com plataforma + canal/autor + data e transcrição clicável que posiciona o player). Verificação: unit (`acervoSource`, `acervoListSort`, URL/filtros/omnibox/VMs web e `webSpeechDownloadFilename`, com os pins da Câmara intocados), int `webSpeechAcervo` (lista só web, facetas, ordenação, tema injetado, rotas privadas no VM, 404 cruzado entre origens, gate de papéis), e2e do `campaignSpeechAcervo` estendido (switcher, card com pill/capa/placeholder, facetas + vazio honesto, ordenação com hint, detalhe com player/transcrição/download/origem, `arquivo`/`capa` com range/download nomeado/negação) e manifest e2e com os vocabulários e shells novos; `pnpm gate:fast`, `pnpm test`, `pnpm build` e os e2e locais (`--workers=1`). Crítica final do `designer` (trigger c) registrada no PR. +**C216 — Terceira fonte do acervo: "Falas na internet" (2026-09-24):** o alternador do acervo passa a ter três fontes — "Falas da Câmara", "Gravações enviadas" e "Falas na internet" — e a terceira abre sobre o catálogo web do C215 com a paridade de busca e filtros da Câmara: busca textual com destaque, `mode=termo|tema`, ano, tema, alcance, duração, municípios citados, contagem, paginação e ordenação. Contrato sem migration e sem twin: o vocabulário de fonte saiu de `recordingListUrl` para o dono puro `lib/acervoSource` (3 vias, desconhecido→Câmara, fail-closed) e o de ordenação para `lib/acervoListSort` (2º consumidor do vocabulário do C219, com o mapeamento de Payload por domínio); `speechListUrl` ganha `source?: 'internet'` e `sort?` de forma **aditiva** (os bytes canônicos da Câmara ficam intocados, a fase é ignorada na web) e `speechListFilters` deriva `origin: web` do estado, mantendo a fase só na Câmara e o gate de duração (`durationSeconds exists`) nos sorts de duração. Loaders e view models ficam nos donos (`speechPageData` ganha `loadWebSpeechAcervoPageData`/`loadWebSpeechDetailPageData`/`loadWebSpeechTitleForActor` e opções de filtro por origem; `speechViewModels` ganha a família `WebSpeech*`) e o detalhe da Câmara passa a filtrar `origin: camara` — um id web vira 404 honesto, e vice-versa. Rotas novas em `/campanha/comunicacao/acervo/internet/[id]` (+ `/arquivo` e `/capa`), precedente `gravacoes/[id]`: a mídia espelhada privada é servida pelo dono `buildPrivateMediaResponse` com gate `canReadCommunicationCatalog`, range/416/404 silencioso e **nome de download derivado do título** (`webSpeechDownloadFilename` fecha o defer S4 do C215); a capa usa a mesma rota autenticada (nunca `/api/media/file`) e o card sem thumbnail renderiza "SEM CAPA". UI (Impeccable C, porte do design aprovado `docs/plans/acervo-fonte-falas-web-ui-design.html`, estendido pelo `designer` na cena 05 com o player de áudio e os chips de plataforma Instagram/Áudio): `AcervoSourceToggle` vira shell de 3 abas em `shared/` (grid de rótulos curtos no mobile), `AcervoSortSelect` compartilhado (serializador escolhido pelo discriminador de fonte dentro do client component — função nunca cruza a fronteira server→client) e `CampaignTranscriptSegmentButton` movido para `shared/` com tipo estrutural; `SpeechAcervoFilters` ganha props de copy (defaults exatos da Câmara, sem Fase na web), `WebSpeechResultCard/List` portam a cena 01 e `WebSpeechDetailPlayer` a 02/05 (vídeo ou áudio nativo no mesmo palco com o badge "Arquivo espelhado · privado", "Baixar", "Abrir na origem", box de origem com plataforma + canal/autor + data e transcrição clicável que posiciona o player). Verificação: unit (`acervoSource`, `acervoListSort`, URL/filtros/omnibox/VMs web e `webSpeechDownloadFilename`, com os pins da Câmara intocados), int `webSpeechAcervo` (lista só web, facetas, ordenação, tema injetado, rotas privadas no VM, 404 cruzado entre origens, gate de papéis), e2e do `campaignSpeechAcervo` estendido (switcher, card com pill/capa/placeholder, facetas + vazio honesto, ordenação com hint, detalhe com player/transcrição/download/origem, `arquivo`/`capa` com range/download nomeado/negação) e manifest e2e com os vocabulários e shells novos; `pnpm gate:fast`, `pnpm test`, `pnpm build` e os e2e locais (`--workers=1`). Crítica final do `designer` (trigger c) **certificada em tier primário** (`Design tier: openai/gpt-5.6-sol`); os 5 bloqueantes de port (painel desktop da barra, ordem das facetas, atribuição inline no mobile, marcador ativo do segmento e vazio da cena 04) foram aplicados e reconfirmados sem bloqueante novo. diff --git a/docs/plans/acervo-fonte-falas-web-impl.md b/docs/plans/acervo-fonte-falas-web-impl.md index a54b471a..c93eea22 100644 --- a/docs/plans/acervo-fonte-falas-web-impl.md +++ b/docs/plans/acervo-fonte-falas-web-impl.md @@ -223,6 +223,10 @@ Aplicados na sessão (não reabrir): extração de `withSeekQuery` (os dois href | D2 | Bytes MP4/MP3 de fixture copiados entre os int specs de fala web (2 sites) | simplify/reuse | 2 | defer_trigger | defer — gatilho: 3º spec de fala web precisar dos bytes | | D3 | `firstParam` idêntico em `acervoSource`/`acervoListSort` (2 linhas) | simplify/quality | 1 | cheap_polish | descartar (camadas impedem dono comum; custa mais que a cópia) | +## Crítica final do designer (trigger c, 2026-09-24) + +`certificado (tier primário — openai/gpt-5.6-sol)`. A primeira crítica encontrou 5 bloqueantes mecânicos (painel desktop da barra, ordem das facetas da web, atribuição inline no mobile, marcador ativo do segmento, vazio da cena 04) + 1 não-bloqueante (play overlay na capa); todos aplicados e reconfirmados sem bloqueante novo. O mobile do omnibox segue o B184 do shell (barra sticky sem borda) — a cena 03 do artefato foi corrigida pelo próprio designer para refletir o shell, sem fork. O play overlay e a variante `web` do segmento (`border-l-2 border-primary`) são o delta visual desta entrega na superfície compartilhada. + ## Self-score de decision-quality **4,5/5.** diff --git a/docs/plans/acervo-fonte-falas-web-ui-design.html b/docs/plans/acervo-fonte-falas-web-ui-design.html index 7dc26848..30fcf2ae 100644 --- a/docs/plans/acervo-fonte-falas-web-ui-design.html +++ b/docs/plans/acervo-fonte-falas-web-ui-design.html @@ -489,13 +489,13 @@ >Internet -
- -
- Assunto, tema ou município… +
+
+ + Assunto, tema ou município…
Termo exatoAno⌄Tema⌄Alcance⌄
-
+

Resultados (contagem)

diff --git a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx index c381fe20..70837fe1 100644 --- a/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx +++ b/src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx @@ -217,17 +217,21 @@ const WebSpeechesSource = ({ return ( - + {/* C216 scene 01: the desktop bar sits inside the approved panel; below + `md` the wrapper disappears and the shared sticky bar stays. */} +
+ +
{data.themeUnavailable ? : null} @@ -264,22 +268,21 @@ const WebSpeechesSource = ({ ) : ( @@ -295,11 +298,11 @@ const WebSpeechesSource = ({ Limpar filtros
- ) : ( - - )} + ) : null} )} diff --git a/src/components/campaign/shared/CampaignTranscriptSegmentButton.tsx b/src/components/campaign/shared/CampaignTranscriptSegmentButton.tsx index 84f3be0d..0681814e 100644 --- a/src/components/campaign/shared/CampaignTranscriptSegmentButton.tsx +++ b/src/components/campaign/shared/CampaignTranscriptSegmentButton.tsx @@ -19,7 +19,8 @@ type TranscriptSegment = { * C200 — the clickable transcript line shared by the plain C199 list, the * grouped C200 blocks and the C216 web detail: seeks the player to the segment * and renders the search highlight. `variant` keeps the approved - * paddings/sizes without a twin. + * paddings/sizes without a twin — the web artifact marks the active segment + * with the red left border while the recordings keep the neutral fill. */ export const CampaignTranscriptSegmentButton = ({ segment, @@ -29,7 +30,7 @@ export const CampaignTranscriptSegmentButton = ({ }: { segment: TranscriptSegment active: boolean - variant?: 'plain' | 'grouped' + variant?: 'plain' | 'grouped' | 'web' onSeek: (seconds: number) => void }) => (