mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
docs(C215-C219): planos do acervo de falas — fonte web, cortes, skill e paridade
This commit is contained in:
parent
3864a823ff
commit
ccc71987c2
8 changed files with 2121 additions and 0 deletions
514
docs/plans/acervo-cortes-fonte-web-ui-design.html
Normal file
514
docs/plans/acervo-cortes-fonte-web-ui-design.html
Normal file
|
|
@ -0,0 +1,514 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — C217 · Cortes de falas na internet</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-destructive: var(--destructive);
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
}
|
||||
[data-theme='campaign'] {
|
||||
--background: #fff;
|
||||
--foreground: #1c1917;
|
||||
--card: #fff;
|
||||
--primary: #c51414;
|
||||
--primary-foreground: #fff;
|
||||
--secondary: #f5f5f4;
|
||||
--muted: #f4f4f5;
|
||||
--muted-foreground: #6b7280;
|
||||
--border: rgb(28 25 23 / 12%);
|
||||
--input: #d6d3d1;
|
||||
--destructive: #b42318;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.grid-bg {
|
||||
background-image: radial-gradient(rgb(28 25 23 / 8%) 0.7px, transparent 0.7px);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 18px 54px rgb(28 25 23 / 10%);
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 2.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.8rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-p {
|
||||
background: #c51414;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-o {
|
||||
border: 1px solid #d6d3d1;
|
||||
background: #fff;
|
||||
}
|
||||
.btn-g {
|
||||
background: transparent;
|
||||
}
|
||||
.ico {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 999px;
|
||||
padding: 0.2rem 0.55rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.field {
|
||||
width: 100%;
|
||||
min-height: 2.75rem;
|
||||
border: 1px solid #d6d3d1;
|
||||
border-radius: 0.5rem;
|
||||
background: #fff;
|
||||
padding: 0.65rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.7rem;
|
||||
}
|
||||
.seg {
|
||||
display: grid;
|
||||
grid-template-columns: 3rem 1fr;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
.selected {
|
||||
border-left: 2px solid #c51414;
|
||||
background: rgb(197 20 20 / 8%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="grid-bg min-h-screen bg-[#efeeec] text-foreground antialiased">
|
||||
<svg class="absolute size-0 overflow-hidden" aria-hidden="true">
|
||||
<symbol id="play" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m6 4 14 8-14 8z" />
|
||||
</symbol>
|
||||
<symbol id="cut" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="6" cy="7" r="3" />
|
||||
<circle cx="6" cy="17" r="3" />
|
||||
<path d="m8.7 8.3 12.6 6.4M8.7 15.7 21.3 9.3" />
|
||||
</symbol>
|
||||
<symbol id="download" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" />
|
||||
</symbol>
|
||||
<symbol id="copy" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</symbol>
|
||||
<symbol id="share" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="18" cy="5" r="3" />
|
||||
<circle cx="6" cy="12" r="3" />
|
||||
<circle cx="18" cy="19" r="3" />
|
||||
<path d="m8.6 10.5 6.8-4M8.6 13.5l6.8 4" />
|
||||
</symbol>
|
||||
<symbol id="check" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m5 12 4 4L19 6" />
|
||||
</symbol>
|
||||
<symbol id="alert" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M12 8v4m0 4h.01" />
|
||||
</symbol>
|
||||
</svg>
|
||||
<main class="mx-auto flex max-w-[1440px] flex-col gap-14 px-4 py-10 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<div class="flex gap-2">
|
||||
<span class="rounded-full bg-primary px-2.5 py-1 text-xs font-semibold text-white"
|
||||
>C217 · gate hi-fi</span
|
||||
><span class="rounded-full border border-border bg-white px-2.5 py-1 text-xs"
|
||||
>Campanha · Comunicação</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight">
|
||||
Da fala web ao corte compartilhável
|
||||
</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
O mesmo gesto de corte da Câmara, agora sobre o arquivo espelhado — sem criar outro
|
||||
editor.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="a">
|
||||
<p class="text-xs font-semibold text-primary">CENA 01</p>
|
||||
<h2 id="a" class="mt-1 text-xl font-semibold">
|
||||
Desktop · detalhe com “Cortar trecho” em destaque
|
||||
</h2>
|
||||
<div class="frame mt-4 rounded-xl border border-border bg-white p-8">
|
||||
<div class="mx-auto max-w-5xl">
|
||||
<a class="text-sm text-muted-foreground">← Falas na internet</a>
|
||||
<div class="mt-5 grid gap-7 lg:grid-cols-[1.2fr_.8fr]">
|
||||
<div>
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center rounded-xl bg-stone-950 text-white"
|
||||
>
|
||||
<button class="grid size-16 place-items-center rounded-full bg-white text-black">
|
||||
<svg class="size-7"><use href="#play" /></svg></button
|
||||
><span class="absolute left-4 top-4 rounded bg-black/60 px-2 py-1 text-xs"
|
||||
>Arquivo espelhado · YouTube</span
|
||||
>
|
||||
<div class="absolute inset-x-4 bottom-4 h-1 rounded bg-white/30">
|
||||
<div class="h-full w-[34%] bg-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button class="btn btn-p">
|
||||
<svg class="ico"><use href="#cut" /></svg>Cortar trecho</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar</button
|
||||
><button class="btn btn-g">Abrir na origem ↗</button>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-muted-foreground">
|
||||
Selecione na transcrição ou ajuste início e fim na janela de corte.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge bg-red-50 text-primary">YouTube</span>
|
||||
<h3 class="mt-3 text-2xl font-semibold">Título da fala publicada na internet</h3>
|
||||
<p class="mt-2 text-sm text-muted-foreground">Canal/autor · dd/mm/aaaa · —:—</p>
|
||||
<p class="mt-6 text-xs uppercase tracking-wide text-muted-foreground">
|
||||
Transcrição · clique nas frases
|
||||
</p>
|
||||
<button class="seg mt-2">
|
||||
<span class="text-xs text-muted-foreground">00:28</span
|
||||
><span class="text-sm">Segmento anterior da fala transcrita.</span></button
|
||||
><button class="seg selected">
|
||||
<span class="text-xs text-primary">00:42</span
|
||||
><span class="text-sm"
|
||||
>Trecho selecionado para virar um corte baixável e compartilhável.</span
|
||||
></button
|
||||
><button class="seg selected">
|
||||
<span class="text-xs text-primary">00:56</span
|
||||
><span class="text-sm">A seleção continua até o fim desta frase.</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="b">
|
||||
<p class="text-xs font-semibold text-primary">CENA 02</p>
|
||||
<h2 id="b" class="mt-1 text-xl font-semibold">Diálogo existente · mínimo 5 s, sem teto</h2>
|
||||
<div
|
||||
class="frame relative mt-4 min-h-[720px] overflow-hidden rounded-xl border border-border bg-stone-900/40 p-6"
|
||||
>
|
||||
<div class="absolute inset-0 bg-white/45 backdrop-blur-[2px]"></div>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
class="relative mx-auto mt-8 max-w-lg rounded-xl border border-border bg-white p-6 shadow-2xl"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold">Cortar vídeo</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
O arquivo sai com o trecho exato — nada antes, nada depois.
|
||||
</p>
|
||||
</div>
|
||||
<button class="text-muted-foreground">×</button>
|
||||
</div>
|
||||
<div class="mt-5 rounded-lg border border-border bg-muted/40 p-3">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="font-mono text-sm">00:42 → 01:08</span
|
||||
><span class="text-xs text-muted-foreground"
|
||||
>26 s · mínimo 5 s · até o fim da fala</span
|
||||
>
|
||||
</div>
|
||||
<div class="relative mt-4 h-2 rounded-full bg-stone-200">
|
||||
<div class="absolute left-[22%] right-[38%] h-2 rounded-full bg-primary"></div>
|
||||
<span
|
||||
class="absolute left-[22%] -top-1.5 size-5 -translate-x-1/2 rounded-full border-2 border-primary bg-white"
|
||||
></span
|
||||
><span
|
||||
class="absolute right-[38%] -top-1.5 size-5 translate-x-1/2 rounded-full border-2 border-primary bg-white"
|
||||
></span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-2 gap-3">
|
||||
<label class="text-xs font-medium"
|
||||
>Início<input class="field mt-1 font-mono" value="00:42" readonly /></label
|
||||
><label class="text-xs font-medium"
|
||||
>Fim<input class="field mt-1 font-mono" value="01:08" readonly
|
||||
/></label>
|
||||
</div>
|
||||
</div>
|
||||
<label class="mt-4 block text-sm font-medium"
|
||||
>Título<input class="field mt-1.5" value="Título do corte" readonly /></label
|
||||
><label class="mt-4 block text-sm font-medium"
|
||||
>Descrição<textarea class="field mt-1.5 min-h-24 resize-none" readonly>
|
||||
Resumo editável do trecho selecionado.</textarea
|
||||
>
|
||||
</label>
|
||||
<p class="mt-3 rounded-lg border border-border px-3 py-2 text-xs text-muted-foreground">
|
||||
Título e descrição podem ser editados antes de publicar.
|
||||
</p>
|
||||
<div class="mt-5 flex justify-end gap-2">
|
||||
<button class="btn btn-o">Cancelar</button
|
||||
><button class="btn btn-p">
|
||||
<svg class="ico"><use href="#cut" /></svg>Cortar e publicar
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted-foreground">
|
||||
Nada é publicado antes de confirmar. O corte ganha um link /corte/<id>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="c">
|
||||
<p class="text-xs font-semibold text-primary">CENA 03</p>
|
||||
<h2 id="c" class="mt-1 text-xl font-semibold">
|
||||
Estados estáticos · Processando → Pronto · Falhou
|
||||
</h2>
|
||||
<div class="mt-4 grid gap-4 lg:grid-cols-3">
|
||||
<article class="frame rounded-xl border border-border bg-white p-5">
|
||||
<span class="badge bg-amber-100 text-amber-800">Processando</span>
|
||||
<h3 class="mt-3 font-semibold">Preparando o corte…</h3>
|
||||
<ol class="mt-4 space-y-2">
|
||||
<li class="step">
|
||||
<svg class="ico text-green-700"><use href="#check" /></svg
|
||||
><span class="text-sm">Arquivo localizado</span
|
||||
><span class="ml-auto text-xs text-muted-foreground">Concluído</span>
|
||||
</li>
|
||||
<li class="step border-primary/40 bg-primary/5">
|
||||
<span
|
||||
class="size-4 animate-spin rounded-full border-2 border-stone-300 border-t-primary"
|
||||
></span
|
||||
><span class="text-sm">Recortando vídeo</span
|
||||
><span class="ml-auto text-xs">Em andamento</span>
|
||||
</li>
|
||||
<li class="step text-muted-foreground">
|
||||
<span class="size-2 rounded-full bg-stone-300"></span
|
||||
><span class="text-sm">Publicando arquivo</span
|
||||
><span class="ml-auto text-xs">Na fila</span>
|
||||
</li>
|
||||
</ol>
|
||||
<p class="mt-4 border-t pt-3 text-xs text-muted-foreground">
|
||||
Nada fica no ar pela metade.
|
||||
</p>
|
||||
</article>
|
||||
<article class="frame rounded-xl border border-border bg-white p-5">
|
||||
<span class="badge bg-green-100 text-green-800">Pronto</span>
|
||||
<h3 class="mt-3 font-semibold">Seu corte está pronto</h3>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
O arquivo já está na biblioteca e o link compartilhável está disponível.
|
||||
</p>
|
||||
<div class="mt-5 flex flex-col gap-2">
|
||||
<button class="btn btn-p">
|
||||
<svg class="ico"><use href="#copy" /></svg>Copiar link</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="frame rounded-xl border border-destructive/30 bg-white p-5">
|
||||
<span class="badge bg-red-100 text-red-800">Falhou</span>
|
||||
<div class="mt-3 flex gap-3">
|
||||
<svg class="size-5 shrink-0 text-destructive"><use href="#alert" /></svg>
|
||||
<div>
|
||||
<h3 class="font-semibold">Não foi possível cortar o trecho</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Nada foi publicado. O mesmo trecho continua selecionado e tentar de novo não cria
|
||||
duplicata.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-p mt-5">Tentar novamente</button
|
||||
><button class="btn btn-o mt-2">Voltar ao acervo</button>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="d">
|
||||
<p class="text-xs font-semibold text-primary">CENA 04</p>
|
||||
<h2 id="d" class="mt-1 text-xl font-semibold">Biblioteca · origem web visível</h2>
|
||||
<div class="frame mt-4 rounded-xl border border-border bg-white p-8">
|
||||
<div class="mx-auto max-w-4xl">
|
||||
<div class="flex items-end justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">Biblioteca de cortes</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Cortes prontos e em processamento no mesmo lugar.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-o">Voltar ao acervo</button>
|
||||
</div>
|
||||
<article class="mt-6 rounded-xl border border-border p-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="badge bg-green-100 text-green-800">Pronto</span
|
||||
><span class="badge border border-red-200 bg-red-50 text-primary"
|
||||
>Origem: YouTube</span
|
||||
><span class="text-xs text-muted-foreground">Criado em dd/mm/aaaa</span>
|
||||
</div>
|
||||
<h4 class="mt-3 text-sm font-semibold">Título do corte da fala web</h4>
|
||||
<p class="mt-1.5 text-xs text-muted-foreground">Título da fala de origem · 00:26</p>
|
||||
<p class="mt-3 text-sm text-foreground/90">Descrição breve e editável do corte.</p>
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
<button class="btn btn-p">
|
||||
<svg class="ico"><use href="#copy" /></svg>Copiar link</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#share" /></svg>WhatsApp</button
|
||||
><button class="btn btn-g ml-auto">Editar</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="e">
|
||||
<p class="text-xs font-semibold text-primary">CENA 05</p>
|
||||
<h2 id="e" class="mt-1 text-xl font-semibold">
|
||||
Página /corte/<id> · unlisted e noindex
|
||||
</h2>
|
||||
<div class="frame mt-4 overflow-hidden rounded-xl border border-border bg-white">
|
||||
<header class="flex h-16 items-center border-b border-border px-8">
|
||||
<img
|
||||
src="../../public/campaign-kit/marca-positiva-completa.png"
|
||||
alt="Jorge Solla 1313 — Mais Saúde, Mais Futuro"
|
||||
class="h-10 w-auto object-contain"
|
||||
/>
|
||||
</header>
|
||||
<main class="mx-auto max-w-4xl px-8 py-10">
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center rounded-xl bg-stone-950 text-white"
|
||||
>
|
||||
<button class="grid size-16 place-items-center rounded-full bg-white text-black">
|
||||
<svg class="size-7"><use href="#play" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mx-auto max-w-3xl pt-7">
|
||||
<h3 class="text-3xl font-bold tracking-tight">Título do corte compartilhável</h3>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
Fala na internet · dd/mm/aaaa · 00:26 · YouTube
|
||||
</p>
|
||||
<p class="mt-5 max-w-[68ch] leading-7">
|
||||
Descrição do trecho, revisada pela assessoria antes da publicação.
|
||||
</p>
|
||||
<div class="mt-7 flex flex-wrap gap-2">
|
||||
<button class="btn btn-p">
|
||||
<svg class="ico"><use href="#share" /></svg>Compartilhar no WhatsApp</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#copy" /></svg>Copiar link</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar arquivo (MP4)
|
||||
</button>
|
||||
</div>
|
||||
<footer
|
||||
class="mt-10 border-t border-border pt-4 text-xs leading-5 text-muted-foreground"
|
||||
>
|
||||
<p>Fonte: YouTube · Canal/autor da publicação</p>
|
||||
<p class="mt-1">
|
||||
Página não listada — o link circula, mas não é indexada nem aparece em buscas.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="f">
|
||||
<p class="text-xs font-semibold text-primary">CENA 06</p>
|
||||
<h2 id="f" class="mt-1 text-xl font-semibold">
|
||||
Mobile ~390 · detalhe e card da biblioteca
|
||||
</h2>
|
||||
<div class="mt-4 flex flex-wrap gap-8">
|
||||
<div class="frame w-[390px] overflow-hidden rounded-[28px] border border-border bg-white">
|
||||
<header class="flex h-14 items-center border-b px-4 text-sm font-semibold">
|
||||
← Detalhe da fala
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center rounded-lg bg-stone-950 text-white"
|
||||
>
|
||||
<svg class="size-8"><use href="#play" /></svg
|
||||
><span class="absolute left-3 top-3 rounded bg-black/60 px-2 py-1 text-[11px]"
|
||||
>YouTube · espelhado</span
|
||||
>
|
||||
</div>
|
||||
<button class="btn btn-p mt-3 w-full">
|
||||
<svg class="ico"><use href="#cut" /></svg>Cortar trecho
|
||||
</button>
|
||||
<div class="mt-5">
|
||||
<h3 class="text-xl font-semibold">Título da fala publicada</h3>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Canal/autor · dd/mm/aaaa</p>
|
||||
</div>
|
||||
<p class="mt-6 text-xs uppercase text-muted-foreground">
|
||||
Transcrição · toque para selecionar
|
||||
</p>
|
||||
<button class="seg selected mt-2">
|
||||
<span class="text-xs text-primary">00:42</span
|
||||
><span class="text-sm leading-6">Trecho selecionado para corte.</span></button
|
||||
><button class="seg selected">
|
||||
<span class="text-xs text-primary">00:56</span
|
||||
><span class="text-sm leading-6">Continuação da seleção.</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="frame w-[390px] overflow-hidden rounded-[28px] border border-border bg-white">
|
||||
<header class="flex h-14 items-center border-b px-4">
|
||||
<p class="text-sm font-semibold">Biblioteca de cortes</p>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<article class="rounded-xl border border-border p-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="badge bg-green-100 text-green-800">Pronto</span
|
||||
><span class="badge border border-red-200 bg-red-50 text-primary">YouTube</span>
|
||||
</div>
|
||||
<h3 class="mt-3 text-sm font-semibold">Título do corte da fala web</h3>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Fala de origem · 00:26</p>
|
||||
<button class="btn btn-p mt-4 w-full">
|
||||
<svg class="ico"><use href="#copy" /></svg>Copiar link
|
||||
</button>
|
||||
<div class="mt-2 grid grid-cols-2 gap-2">
|
||||
<button class="btn btn-o">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#share" /></svg>WhatsApp
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
106
docs/plans/acervo-cortes-fonte-web.md
Normal file
106
docs/plans/acervo-cortes-fonte-web.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# C217 — Cortes e links compartilháveis na fonte "Falas na internet"
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-23
|
||||
Issue: #1293
|
||||
Priority: P2
|
||||
Impeccable: C — fluxo de corte em fonte nova dentro da vertical /campanha/comunicacao/acervo
|
||||
Design UI: docs/plans/acervo-cortes-fonte-web-ui-design.html
|
||||
Appetite: ~2–3 dias eng; um outcome verificável — a assessoria corta um trecho de uma fala da internet, o corte aparece na biblioteca e o link compartilhado abre no ponto certo, com download
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O acervo ganhou uma fonte nova: falas do Solla que circulam na internet (YouTube, rádio, Instagram …), trazidas pelo C215 e navegáveis pelo C216. Mas cortar, guardar na biblioteca, compartilhar por link e baixar hoje só existem para as falas da Câmara — quem acha uma fala da internet no acervo só consegue link para fora. O dono (2026-09-23): "as funcionalidades de baixar e realizar cortes (que são então disponibilizados via links compartilháveis e na biblioteca de cortes) devem também funcionar nesta sessão". Esta fatia estende o corte que já existe à fonte web, sem mecanismo novo: mesmo diálogo, mesma biblioteca, mesma página `/corte/<id>`.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação (`communicator`, mais `coordinator`/`candidate`), na mesa ou em campo, montando peça a partir de uma fala que viu na internet.
|
||||
- **Job principal:** cortar o trecho exato de uma fala da internet e ter o corte baixável, compartilhável e reencontrável na biblioteca — sem sair da ferramenta e sem refazer na mão.
|
||||
- **Fluxo desejado:** abre a fala web no detalhe (C216) → "Cortar trecho" abre a janela de corte que já conhece (mínimo 5 s, sem teto) → confirma e acompanha Processando → Pronto → "Baixar", "Copiar link" ou "Compartilhar no WhatsApp" → o corte está na biblioteca com a origem visível e o link `/corte/<id>` abre no ponto certo.
|
||||
- **Anti-goals de produto:** não vira editor de vídeo nem segundo mecanismo de corte/player; não muda a ingestão nem a fonte (C215/C216); não cria galeria pública; não publica em rede social; não duplica a biblioteca.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[detalhe da fala web (C216)] → "Cortar trecho" → janela de corte (≥5s, sem teto)
|
||||
→ Processando → Pronto → [Baixar] · [Copiar link] · [Compartilhar no WhatsApp]
|
||||
→ [biblioteca de cortes: mesma lista, origem visível "YouTube"/"Rádio"/"Instagram"]
|
||||
→ [/corte/<id> unlisted + noindex: player e download]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/acervo-cortes-fonte-web-ui-design.html` — cenas: detalhe da fala web com os gestos, diálogo "Cortar trecho", card na biblioteca com a origem e a página `/corte/<id>`.
|
||||
|
||||
```text
|
||||
[detalhe da fala web] [biblioteca: card do corte]
|
||||
player + transcrição título · origem (YouTube) · duração
|
||||
[Cortar trecho] [Baixar] ─→ corte MP4 ─→ [Baixar] [Copiar link] [WhatsApp]
|
||||
```
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- No detalhe de uma fala da fonte web (C216), "Cortar trecho" abre a janela de corte existente e gera o corte a partir do arquivo espelhado — sem depender da plataforma de origem.
|
||||
- O corte entra na mesma biblioteca `/campanha/comunicacao/acervo/cortes`, com a origem visível no card; paridade total com o corte da Câmara: download, link, editar título/descrição, apagar com confirmação, "Tentar novamente" no estado falhou e kill switch de publicação (C168/C183).
|
||||
- O link compartilhado é o mesmo contrato `/corte/<id>` (unlisted, `noindex`), com player, download, "Copiar link" e "Compartilhar no WhatsApp"; a página abre o corte certo independente da origem.
|
||||
- A janela de corte é a mesma: mínimo de 5 s, sem teto (C170).
|
||||
- O crédito da página acompanha a origem real da fala — nunca exibir o crédito da Câmara para fala de terceiro.
|
||||
- **Guardrails:** gate `speechCatalog` intocado (communicator/coordinator/candidate; advisor/leader fail-closed); fala web sem arquivo espelhado não oferece o corte (questão 3); sem segundo mecanismo de mídia nem de compartilhamento; `/corte/<id>` continua não listado.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — superfície de ação (cortar/baixar/compartilhar) sobre a lista paginada existente; o estado do corte é atributo do item, não KPI.
|
||||
- **Decisões desbloqueadas:** a assessoria decide qual trecho de uma fala da internet vira peça e se baixa/compartilha; nada agregado.
|
||||
- **Forma:** _adiada ao plano de implementação_ — restrição de produto: sem métricas de vaidade (views/downloads) nem contadores de acervo.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Gestos no detalhe da fala web (mesmos rótulos da Câmara):** "Cortar trecho" (abre a janela existente) · "Baixar" (arquivo da fala) · no corte pronto, "Copiar link" e "Compartilhar no WhatsApp".
|
||||
- **Janela de corte:** mínimo 5 s, sem teto — a mesma regra do C170 (até o fim da fala).
|
||||
- **Geração:** o corte usa o arquivo espelhado da fala (C215) — sem resolver VOD nem depender da plataforma de origem.
|
||||
- **Origens visíveis (rótulo no card da biblioteca):** "YouTube", "Rádio", "Instagram", … (a taxonomia real é do C215/C216).
|
||||
- **Link do corte:** mesmo contrato `/corte/<id>` da Câmara — unlisted e `noindex`; a decisão sobre origem de terceiros fica na questão 1.
|
||||
- **Paridade de biblioteca (C168/C183):** editar título/descrição, apagar com confirmação, "Tentar novamente" no falhou e kill switch de publicação.
|
||||
- **Instrução do dono (verbatim, 2026-09-23):** "Você não deve se preocupar com direitos da midia, faça o que for necessario para viabilizar a analise e download das midias que forem encontradas para alimentar esta sessão e para viabilizar as funcionalidades pediadas." — decisão confirmada no gate (2026-09-24); vale para os cortes desta fonte e refere C215/C212.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** detalhe da fala web (do C216); entrada/criação do corte (`src/app/(campaign)/campanha/actions/speech.ts` + schemas); pipeline `src/utilities/speech/speechCutJob.ts` (hoje resolve VOD da Câmara, baixa, corta com ffmpeg e grava MP4 na `media` pública); lista `speechCutListUrl.ts`/`speechCutPageData.ts` (origem no card); `src/collections/SpeechCut.ts` (relação/origem); página `src/app/(frontend)/corte/[id]/page.tsx` + `src/components/SpeechCutShareActions.tsx` (link e crédito); donos de mídia `src/utilities/media/ffmpeg.ts` e `src/utilities/privateMedia/privateMediaResponse.ts`.
|
||||
- **Precedente a olhar:** C167 (corte/publicação), C168/C183 (biblioteca/apagar/retry), C170 (janela sem teto), `acervo-gravacoes-enviadas.md` (C199 excluiu corte das gravações — "C167/C174 são da Câmara"; a instrução do dono reabre esse corte para a fonte web).
|
||||
- **Risco de acoplamento:** o corte já é um MP4 armazenado (não depende do YouTube para tocar/baixar); o acoplamento à Câmara está na **entrada** (coordenadas/VOD) e no **crédito** — o executor escolhe onde estender sem quebrar o caminho Câmara; mídia grande/privada é do C215/C193 (não criar mecanismo gêmeo); serializar com o C216 nos arquivos do detalhe.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Dura: C215** — a ingestão espelha a fala e o arquivo; sem arquivo não há de onde cortar.
|
||||
- **Dura: C216** — a fonte/lista/detalhe onde o gesto nasce; serializar nos arquivos do detalhe.
|
||||
- Observação: a guardrail "mídia de terceiro nunca é baixada" (C199/C211) é superada para esta sessão pela instrução do dono (ver literais), registrada em C215/C212.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Ingestão e espelhamento do arquivo (C215) e fonte/lista/detalhe/player da fala web (C216).
|
||||
- Skill de comunicação (C218) e paridade de filtros das gravações (C219).
|
||||
- Editor de vídeo, recorte fino/timeline, legendas e re-render.
|
||||
- Publicação em rede social e galeria pública de cortes.
|
||||
- Diarização/rotulagem de quem fala.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Editor de corte multi-trilha.** Se alguém "só completar": timeline, faixas, preview de transição. **Corte neste item:** a mesma seleção de trecho da Câmara, agora sobre o arquivo espelhado.
|
||||
- **Render na nuvem / fila própria de encode.** Se alguém "só completar": worker dedicado, streaming, partes. **Corte neste item:** o pipeline de corte existente, aplicado ao arquivo da fonte web.
|
||||
- **Galeria pública de cortes.** Se alguém "só completar": índice indexável, feed, perfil. **Corte neste item:** `/corte/<id>` unlisted como hoje; sem listagem pública.
|
||||
- **Segundo mecanismo de compartilhamento.** Se alguém "só completar": embed, SDK, outras redes. **Corte neste item:** "Copiar link" + "Compartilhar no WhatsApp".
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Link compartilhável de corte de origem web?** **Opções:** A) mesma página pública `/corte/<id>` (paridade com a Câmara; risco de direitos coberto pela instrução do dono) | B) link interno autenticado só para a campanha | C) público só quando a origem for própria (canal/perfil do Solla ou material da campanha) e interno para terceiros. **Recomendação: A** — o dono pediu paridade ("links compartilháveis"); registrar o risco assumido. _(confirmado no gate, 2026-09-24)_
|
||||
- **Filtro por origem na biblioteca de cortes?** **Opções:** A) só exibir a origem no card nesta fase | B) filtro por plataforma já aqui. **Recomendação: A** — a lista hoje é q+page; filtro por origem é paridade de filtros (C219) e não é preciso para o outcome. _(confirmado no gate, 2026-09-24)_
|
||||
- **Corte de fala web sem arquivo espelhado?** **Opções:** A) gesto indisponível (só link de origem) | B) resolver/baixar sob demanda. **Recomendação: A** — a ingestão (C215) garante o arquivo; sem arquivo não há corte, e resolver sob demanda recria o acoplamento à plataforma. _(confirmado no gate, 2026-09-24)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue: — (após `pnpm agent:register`)
|
||||
- Design UI (gate): `docs/plans/acervo-cortes-fonte-web-ui-design.html`
|
||||
- Planos irmãos: C215 (ingestão) · C216 (fonte/lista/detalhe) · C218 (skill) · C219 (paridade de filtros); [`c168-biblioteca-cortes.md`](c168-biblioteca-cortes.md) · [`c170-corte-sem-limite.md`](c170-corte-sem-limite.md) · [`acervo-encontrar-cortes.md`](acervo-encontrar-cortes.md) · [`corte-pagina-publica-design.md`](corte-pagina-publica-design.md) · [`acervo-gravacoes-enviadas.md`](acervo-gravacoes-enviadas.md)
|
||||
- Arquivos-pista: `src/collections/SpeechCut.ts` · `src/app/(campaign)/campanha/actions/speech.ts` · `src/utilities/speech/speechCutJob.ts` · `src/utilities/speech/speechCutListUrl.ts` · `src/utilities/speech/speechCutPageData.ts` · `src/lib/speechCut.ts` · `src/app/(frontend)/corte/[id]/page.tsx` · `src/components/SpeechCutShareActions.tsx` · `src/utilities/media/ffmpeg.ts` · `src/utilities/privateMedia/privateMediaResponse.ts`
|
||||
- `AGENTS.md` — vertical Comunicação, gate `speechCatalog`, leader lockdown.
|
||||
89
docs/plans/acervo-falas-web-ingestao.md
Normal file
89
docs/plans/acervo-falas-web-ingestao.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# C215 — Ingestão de falas da internet no acervo (descoberta, espelho e catálogo)
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-23
|
||||
Issue: #1291
|
||||
Priority: P1
|
||||
Impeccable: A — N/A (dados/ops, sem UI)
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~3–4 dias de engenharia + tempo de máquina; um outcome verificável — um lote de falas encontradas na web entra no catálogo com transcrição, facetas e mídia espelhada, reexecutável sem duplicar e com relatório honesto
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O acervo de falas do Solla hoje só enxerga a Câmara. O que o deputado disse em vídeo no YouTube, em Reels, em rádio e em áudio solto existe na internet e não existe para a busca: a assessoria não encontra "onde ele falou de X" fora do Plenário. O dono decidiu catalogar esse material com a mesma experiência do "Falas da Câmara" — mesmas facetas, mesmos filtros — e alimentar o catálogo a partir de uma varredura da web.
|
||||
|
||||
Este item entrega a esteira de ingestão: recebe um lote de achados (a descoberta é a skill do C218), baixa/espelha a mídia, transcreve, classifica e grava de forma idempotente, com relatório. A tela é o C216 e os cortes são o C217; aqui o acervo nasce e sobrevive a reexecução.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** coordenação técnica rodando a ingestão (uma vez no início, depois por janela); a consumidora final é a assessoria de comunicação, na tela do C216.
|
||||
- **Job principal:** entregar um lote de achados da web e obter cada fala catalogada — transcrita, facetada, com mídia preservada — podendo reexecutar sem duplicar.
|
||||
- **Fluxo desejado:** apontar o lote de achados (plataforma + URL + metadados que a descoberta já trouxe) → a esteira baixa a mídia, transcreve, classifica e grava → imprime relatório (achados, novos, atualizados, ignorados, falhas, tempo e custo de ASR) → a fala aparece no acervo com as mesmas facetas do "Falas da Câmara".
|
||||
- **Anti-goals de produto:** não é crawler nem serviço de monitoramento (não descobre sozinho); não é CMS de mídia; não espelha nada para o público; não vira segundo cadastro de pessoa.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Cada fala da web vira registro com: data, duração, transcrição com timestamps, temas e alcance (mesmas facetas do "Falas da Câmara"), menções (municípios/pessoas/programas/projetos), proveniência da classificação, origem (plataforma + URL) e canal/autor.
|
||||
- A mídia é espelhada para o acervo interno (player/download/cortes) e nunca vira URL pública; o arquivo é preservado.
|
||||
- Reexecutar não duplica (chave natural por origem + id externo/URL canônica) nem sobrescreve curadoria manual; a ingestão aceita janela para a atualização incremental do C218.
|
||||
- Relatório por execução: achados, novos, atualizados, ignorados, falhas, tempo e custo de ASR — falha não é maquiada.
|
||||
- **Guardrails:** sem segundo cadastro de pessoa (menções são texto, como no C153); sem espelho público; sem PII; sem score de vaidade; sem publicação externa; não tocar o feed do site público nem a Central de Conteúdos (C211 é dono da peça; aqui é catálogo de fala); leitura restrita ao gate já existente (`canReadCommunicationCatalog`, fail-closed).
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Sim, derivado — facetas e menções alimentam a busca e os filtros do C216; aqui nenhum agregado é apresentado.
|
||||
- **Decisões desbloqueadas:** a assessoria decide qual fala/trecho usar por tema, alcance, data, município citado e canal de origem.
|
||||
- **Forma:** _adiada ao plano de implementação_ — restrição de produto: leitura relativa/local, sem dashboard de vaidade.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Instrução do dono (verbatim, 2026-09-23):** "Você não deve se preocupar com direitos da mídia, faça o que for necessário para viabilizar a análise e download das mídias que forem encontradas." — supera o guardrail C212/C211-FOLLOWUP ("mídia de terceiro nunca baixada/persistida") **para este catálogo interno**. **Confirmado no gate (2026-09-24):** risco assumido e registrado.
|
||||
- **Campos obrigatórios do catálogo:** data, duração, transcrição com timestamps, temas, alcance, menções (municípios/pessoas/programas/projetos), proveniência da classificação, origem (plataforma + URL), canal/autor.
|
||||
- **Mídia espelhada:** privada e preservada, servida só ao acervo interno; nunca URL pública.
|
||||
- **Idempotência:** chave natural por origem + id externo/URL canônica; curadoria manual nunca sobrescrita.
|
||||
- **Relatório por execução:** achados · novos · atualizados · ignorados · falhas · tempo · custo de ASR.
|
||||
- **Janela:** a ingestão aceita recorte temporal; a execução inicial cobre tudo que a descoberta alcançar.
|
||||
- **Falha de download/transcrição:** fica fora da lista e aparece no relatório (reprocessar = rodar de novo).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** script de ingestão em `scripts/` no padrão de import idempotente; ASR em `src/utilities/ai/deepInfraTranscribe.ts`; classificação em `src/utilities/speech/speechClassifier.ts` (facetas em `src/lib/speechFacets.ts`); `src/utilities/media/ffmpeg.ts`; mídia privada em `src/utilities/privateMedia/privateMediaResponse.ts`; gate de leitura já existente.
|
||||
- **Precedente a olhar:** `scripts/import-camara-speeches.mjs` (match por `sourceKey`, skip do já guardado, faceta manual preservada, `--dry-run`, `--date/--legislature/--all`, relatórios gitignored, guard de banco local + `*_CONFIRM=1`) e `src/utilities/speech/speechImport.ts` (upsert transacional); C211 (`contentPiece` com `sourceUrl`/`origin`) como vizinho que não se invade.
|
||||
- **Risco de acoplamento:** `speech` é a fonte Câmara e seu `sourceKey` é identidade por contrato — a fala da web não pode quebrá-lo; `speechClassifier` e `deepInfraTranscribe` têm dono único — reusar, nunca duplicar.
|
||||
- **Fatos verificados (ponto de partida):** não existe yt-dlp/gallery-dl no repo nem busca SERP no app; a pesquisa web é feita por subagentes de skill (padrão `research.json`). Ferramenta de download e destino do espelho são decisão do plano de implementação.
|
||||
|
||||
## Dependências
|
||||
|
||||
- Nenhuma dura — reusa ASR, classificador, ffmpeg e import idempotente já entregues (C153/C199/C211).
|
||||
- C216 (tela), C217 (cortes) e C218 (skill de atualização) dependem deste.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Tela da fonte de falas na web (C216); cortes e links (C217); skill de atualização incremental (C218); paridade de filtros das gravações (C219).
|
||||
- Diarização, edição de transcrição e publicação.
|
||||
- Descoberta de achados (é a skill C218); publicação externa; feed do site; Central de Conteúdos.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"A ingestão descobre sozinha".** Se alguém "só completar": crawler genérico/serviço de monitoramento. **Corte neste item:** recebe lote de achados; a descoberta é C218.
|
||||
- **"Baixar tudo de todos os perfis".** Se alguém "só completar": arquivo infinito e ruído. **Corte neste item:** ingere o lote que a descoberta entregar.
|
||||
- **"Classificação perfeita antes de importar".** Se alguém "só completar": curadoria manual interminável. **Corte neste item:** classificação automática com proveniência + correção depois (padrão C153).
|
||||
- **"Espelhar em qualidade máxima".** Se alguém "só completar": disco e tempo sem necessidade. **Corte neste item:** qualidade suficiente para transcrição/player/corte; decisão da implementação.
|
||||
- **"Segundo pipeline de transcrição".** Se alguém "só completar": dois ASR, duas facetas divergindo. **Corte neste item:** donos únicos reusados.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Direitos/ToS:** **Opções:** A) seguir a instrução do dono — scraping + download + espelho privado, com o guardrail C212/C211-FOLLOWUP registrado como superado neste catálogo | B) só caminhos oficiais — inviabiliza download/análise/cortes | C) híbrido (conta/canal próprios + anexo manual; terceiros só link). **Confirmado no gate (2026-09-24): A** — a instrução do dono prevalece; guardrail C212/C211-FOLLOWUP superado neste catálogo interno; risco assumido e registrado.
|
||||
- **Plataformas v1:** **Opções:** A) YouTube + Instagram + áudio/rádio (RSS/MP3/embed) | B) + TikTok/Facebook/X. **Recomendação:** A — cobre o que existe hoje; o resto quando doer. _(confirmado no gate, 2026-09-24)_
|
||||
- **Janela da varredura inicial:** **Opções:** A) tudo que a descoberta alcançar | B) desde 2011 (janela do acervo Câmara) | C) desde 2022. **Recomendação:** A — "tudo que for encontrado", como pediu o dono. _(confirmado no gate, 2026-09-24)_
|
||||
- **Ferramenta de download e onde a mídia espelhada mora:** _adiada ao plano de implementação_ — restrição de produto: privada, preservada, servida só ao acervo; sem URL pública. _(confirmado no gate, 2026-09-24)_
|
||||
- **Falha de download/transcrição:** **Opções:** A) item fica fora da lista e aparece no relatório | B) estado "Falhou" visível com reprocessar. **Recomendação:** A nesta fase — a lista só mostra o que está pronto; reprocessar é rodar de novo. _(confirmado no gate, 2026-09-24)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue: — (após `pnpm agent:register`)
|
||||
- Design UI: N/A
|
||||
- `docs/plans/catalogo-falas-solla.md` (C153 — catálogo e facetas) · `docs/plans/backfill-acervo-falas.md` (C155) · `docs/plans/central-conteudos-ingestao.md` (C211) · `docs/plans/central-conteudos-varredura-instagram.md` (C212 — §Achados, decisão anti-scraping)
|
||||
- Arquivos-pista: `scripts/import-camara-speeches.mjs` · `src/utilities/speech/speechImport.ts` · `src/utilities/speech/speechClassifier.ts` · `src/lib/speechFacets.ts` · `src/utilities/ai/deepInfraTranscribe.ts` · `src/utilities/media/ffmpeg.ts` · `src/utilities/privateMedia/privateMediaResponse.ts` · `src/utilities/campaignAccess.ts`
|
||||
- `AGENTS.md` (convenções de access, mídia privada e gate de leitura do acervo)
|
||||
587
docs/plans/acervo-fonte-falas-web-ui-design.html
Normal file
587
docs/plans/acervo-fonte-falas-web-ui-design.html
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — C216 · Falas na internet</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
}
|
||||
[data-theme='campaign'] {
|
||||
--background: #fff;
|
||||
--foreground: #1c1917;
|
||||
--card: #fff;
|
||||
--primary: #c51414;
|
||||
--primary-foreground: #fff;
|
||||
--secondary: #f5f5f4;
|
||||
--muted: #f4f4f5;
|
||||
--muted-foreground: #6b7280;
|
||||
--border: rgb(28 25 23 / 12%);
|
||||
--input: #d6d3d1;
|
||||
--ring: #c51414;
|
||||
--destructive: #b42318;
|
||||
--sidebar: #fafaf9;
|
||||
--sidebar-border: rgb(28 25 23 / 12%);
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 18px 54px rgb(28 25 23 / 10%);
|
||||
}
|
||||
.grid-bg {
|
||||
background-image: radial-gradient(rgb(28 25 23 / 8%) 0.7px, transparent 0.7px);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 2.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-p {
|
||||
background: #c51414;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-o {
|
||||
border: 1px solid #d6d3d1;
|
||||
background: #fff;
|
||||
}
|
||||
.btn-g {
|
||||
background: transparent;
|
||||
}
|
||||
.ico {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
min-height: 2rem;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 0.25rem 0.7rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.seg {
|
||||
display: grid;
|
||||
grid-template-columns: 3rem 1fr;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
.seg.active {
|
||||
border-left: 2px solid #c51414;
|
||||
background: rgb(197 20 20 / 8%);
|
||||
}
|
||||
mark {
|
||||
border-radius: 0.2rem;
|
||||
background: #fef08a;
|
||||
padding: 0 0.12rem;
|
||||
color: inherit;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
scroll-behavior: auto !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="grid-bg min-h-screen bg-[#efeeec] text-foreground antialiased">
|
||||
<svg class="absolute size-0 overflow-hidden" aria-hidden="true">
|
||||
<symbol id="search" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</symbol>
|
||||
<symbol id="play" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m6 4 14 8-14 8z" />
|
||||
</symbol>
|
||||
<symbol id="video" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2" />
|
||||
<path d="m10 9 5 3-5 3z" />
|
||||
</symbol>
|
||||
<symbol id="download" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" />
|
||||
</symbol>
|
||||
<symbol id="external" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
</symbol>
|
||||
<symbol id="lock" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<rect x="3" y="11" width="18" height="10" rx="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</symbol>
|
||||
<symbol id="x" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m18 6-12 12M6 6l12 12" />
|
||||
</symbol>
|
||||
</svg>
|
||||
<main class="mx-auto flex max-w-[1480px] flex-col gap-14 px-4 py-10 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<div class="flex gap-2">
|
||||
<span class="rounded-full bg-primary px-2.5 py-1 text-xs font-semibold text-white"
|
||||
>C216 · gate hi-fi</span
|
||||
><span class="rounded-full border border-border bg-white px-2.5 py-1 text-xs"
|
||||
>Campanha · Comunicação</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight">
|
||||
Falas na internet, no mesmo acervo
|
||||
</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
A assessoria encontra, ouve e baixa uma fala já catalogada, sabendo exatamente de onde ela
|
||||
veio.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="s1">
|
||||
<div class="mb-4">
|
||||
<p class="text-xs font-semibold text-primary">CENA 01</p>
|
||||
<h2 id="s1" class="mt-1 text-xl font-semibold">
|
||||
Desktop ~1280 · lista da terceira fonte
|
||||
</h2>
|
||||
</div>
|
||||
<div class="frame overflow-x-auto rounded-xl border border-border bg-white">
|
||||
<div class="flex min-h-[920px] min-w-[1180px]">
|
||||
<aside
|
||||
class="flex w-60 shrink-0 flex-col border-r border-sidebar-border bg-sidebar p-3"
|
||||
>
|
||||
<div class="flex h-12 items-center gap-2 px-2">
|
||||
<div
|
||||
class="grid size-8 place-items-center rounded-lg bg-primary font-bold text-white"
|
||||
>
|
||||
S
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Solla</p>
|
||||
<p class="text-xs text-muted-foreground">Campanha</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="mt-4 space-y-1 text-sm">
|
||||
<div class="rounded-md px-3 py-2 text-muted-foreground">Início</div>
|
||||
<div class="rounded-md px-3 py-2 text-muted-foreground">Municípios</div>
|
||||
<div class="rounded-lg bg-secondary p-1">
|
||||
<div class="px-2 py-2 font-medium">Comunicação</div>
|
||||
<div class="ml-4 border-l border-border pl-3">
|
||||
<div
|
||||
class="rounded-md bg-white px-2 py-2 font-medium text-primary ring-1 ring-border"
|
||||
>
|
||||
Acervo de falas
|
||||
</div>
|
||||
<div class="px-2 py-2 text-muted-foreground">Biblioteca de cortes</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<p class="mt-auto border-t border-border px-2 pt-3 text-xs text-muted-foreground">
|
||||
Assessoria · Comunicação
|
||||
</p>
|
||||
</aside>
|
||||
<div class="min-w-0 flex-1">
|
||||
<header class="flex h-16 items-center border-b border-border px-8">
|
||||
<div>
|
||||
<p class="font-semibold">Acervo de falas</p>
|
||||
<p class="text-xs text-muted-foreground">Comunicação</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mx-auto max-w-[900px] px-8 py-7">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold tracking-tight">Acervo de falas</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Busque nas falas da Câmara, nas gravações da equipe ou nas falas encontradas na
|
||||
internet.
|
||||
</p>
|
||||
</div>
|
||||
<nav
|
||||
class="mt-5 inline-flex rounded-lg bg-secondary p-1 text-sm"
|
||||
aria-label="Fonte do acervo"
|
||||
>
|
||||
<a class="rounded-md px-3 py-2 text-muted-foreground">Falas da Câmara</a
|
||||
><a class="rounded-md px-3 py-2 text-muted-foreground">Gravações enviadas</a
|
||||
><a
|
||||
aria-current="page"
|
||||
class="rounded-md bg-white px-3 py-2 font-medium shadow-sm ring-1 ring-border"
|
||||
>Falas na internet</a
|
||||
>
|
||||
</nav>
|
||||
<form class="mt-5 rounded-xl border border-border bg-card p-4 shadow-sm">
|
||||
<label class="text-sm font-medium">Buscar nas falas na internet</label>
|
||||
<div
|
||||
class="mt-1.5 flex min-h-11 items-center gap-2 rounded-md border border-input px-3"
|
||||
>
|
||||
<svg class="ico text-muted-foreground"><use href="#search" /></svg
|
||||
><input
|
||||
class="min-w-0 flex-1 bg-transparent text-sm outline-none"
|
||||
value="defesa da saúde pública"
|
||||
readonly
|
||||
/><button class="text-muted-foreground">
|
||||
<svg class="ico"><use href="#x" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 flex items-center justify-between gap-3 border-t border-border pt-3"
|
||||
>
|
||||
<span class="text-xs font-medium text-muted-foreground">Buscar por</span>
|
||||
<div class="inline-flex rounded-lg bg-muted p-1 text-sm">
|
||||
<button class="rounded-md px-3 py-2 text-muted-foreground">Termo exato</button
|
||||
><button
|
||||
class="rounded-md bg-white px-3 py-2 font-semibold text-primary shadow-sm ring-1 ring-border"
|
||||
>
|
||||
✦ Por tema
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Encontra falas relacionadas pelo sentido.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button class="chip">Ano⌄</button
|
||||
><button class="chip border-primary/30 bg-primary/5 text-primary">
|
||||
Tema: Saúde⌄</button
|
||||
><button class="chip">Alcance⌄</button><button class="chip">Duração⌄</button
|
||||
><button class="chip">Município citado⌄</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-5 flex items-center justify-between">
|
||||
<p class="text-sm font-semibold">
|
||||
Resultados encontrados
|
||||
<span class="font-normal text-muted-foreground">(contagem)</span>
|
||||
</p>
|
||||
<select class="rounded-md border border-input bg-white px-3 py-2 text-sm">
|
||||
<option>Mais recentes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt-3 space-y-3">
|
||||
<article
|
||||
class="grid grid-cols-[152px_1fr_auto] gap-4 rounded-xl border border-border p-4"
|
||||
>
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center overflow-hidden rounded-lg bg-stone-800 text-white"
|
||||
>
|
||||
<svg class="size-8 opacity-80"><use href="#play" /></svg
|
||||
><span class="absolute left-2 top-2 rounded bg-black/65 px-2 py-1 text-[11px]"
|
||||
>YouTube</span
|
||||
><span
|
||||
class="absolute inset-x-2 bottom-2 text-center text-[9px] font-semibold text-white/75"
|
||||
>NEEDS ASSET · THUMBNAIL DA ORIGEM</span
|
||||
>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="rounded-full bg-red-50 px-2 py-1 text-xs font-semibold text-primary"
|
||||
>YouTube</span
|
||||
><span class="text-xs text-muted-foreground">dd/mm/aaaa · —:—</span>
|
||||
</div>
|
||||
<h4 class="mt-2 text-sm font-semibold">
|
||||
Título da fala publicada na internet
|
||||
</h4>
|
||||
<p class="mt-2 text-sm leading-6">
|
||||
… compromisso com a <mark>saúde pública</mark> e com o atendimento de quem
|
||||
mais precisa …
|
||||
</p>
|
||||
<div class="mt-2 flex gap-1.5">
|
||||
<span class="rounded-md bg-muted px-2 py-1 text-xs">Saúde</span
|
||||
><span class="rounded-md bg-muted px-2 py-1 text-xs">Bahia</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-p self-end">
|
||||
<svg class="ico"><use href="#play" /></svg>Ver fala
|
||||
</button>
|
||||
</article>
|
||||
<article
|
||||
class="grid grid-cols-[152px_1fr_auto] gap-4 rounded-xl border border-border p-4"
|
||||
>
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center rounded-lg bg-stone-200 text-stone-500"
|
||||
>
|
||||
<svg class="size-7"><use href="#video" /></svg
|
||||
><span class="absolute bottom-2 text-[10px] font-semibold">SEM CAPA</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="rounded-full bg-blue-50 px-2 py-1 text-xs font-semibold text-blue-800"
|
||||
>Rádio</span
|
||||
><span class="text-xs text-muted-foreground">dd/mm/aaaa · —:—</span>
|
||||
</div>
|
||||
<h4 class="mt-2 text-sm font-semibold">Entrevista sobre um tema público</h4>
|
||||
<p class="mt-2 text-sm leading-6">
|
||||
… outro trecho em que o assunto buscado aparece com <mark>clareza</mark> …
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-o self-end">Ver fala</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s2">
|
||||
<div class="mb-4">
|
||||
<p class="text-xs font-semibold text-primary">CENA 02</p>
|
||||
<h2 id="s2" class="mt-1 text-xl font-semibold">Desktop · detalhe da fala web</h2>
|
||||
</div>
|
||||
<div class="frame rounded-xl border border-border bg-white p-8">
|
||||
<div class="mx-auto max-w-5xl">
|
||||
<a class="text-sm text-muted-foreground">← Voltar para Falas na internet</a>
|
||||
<div class="mt-5 grid gap-7 lg:grid-cols-[1.2fr_.8fr]">
|
||||
<div>
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center overflow-hidden rounded-xl bg-stone-950 text-white"
|
||||
>
|
||||
<button
|
||||
class="grid size-16 place-items-center rounded-full bg-white text-stone-950 shadow-xl"
|
||||
>
|
||||
<svg class="size-7"><use href="#play" /></svg>
|
||||
</button>
|
||||
<div class="absolute inset-x-4 bottom-4 h-1 rounded-full bg-white/30">
|
||||
<div class="h-full w-[38%] rounded-full bg-primary"></div>
|
||||
</div>
|
||||
<span
|
||||
class="absolute left-4 top-4 rounded-md border border-white/20 bg-black/50 px-2 py-1 text-xs"
|
||||
>Arquivo espelhado · privado</span
|
||||
>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button class="btn btn-p">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar</button
|
||||
><button class="btn btn-o">
|
||||
<svg class="ico"><use href="#external" /></svg>Abrir na origem
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-4 rounded-lg border border-border bg-muted/40 p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Origem
|
||||
</p>
|
||||
<p class="mt-1 text-sm font-medium">YouTube · Canal/autor da publicação</p>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Publicado em dd/mm/aaaa</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="rounded-full bg-red-50 px-2 py-1 text-xs font-semibold text-primary"
|
||||
>YouTube</span
|
||||
>
|
||||
<h3 class="mt-3 text-2xl font-semibold tracking-tight">
|
||||
Título da fala publicada na internet
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-muted-foreground">—:— · tema e alcance classificados</p>
|
||||
<section class="mt-6">
|
||||
<p class="text-xs uppercase tracking-wide text-muted-foreground">
|
||||
Transcrição · clique para posicionar
|
||||
</p>
|
||||
<ol class="mt-2 space-y-1">
|
||||
<li>
|
||||
<button class="seg">
|
||||
<span class="text-xs tabular-nums text-muted-foreground">00:00</span
|
||||
><span class="text-sm leading-6"
|
||||
>Início da fala transcrita, exibido em segmentos clicáveis.</span
|
||||
>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="seg active">
|
||||
<span class="text-xs tabular-nums text-primary">00:12</span
|
||||
><span class="text-sm leading-6"
|
||||
>O trecho ativo acompanha a posição do player e mantém o contexto.</span
|
||||
>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="seg">
|
||||
<span class="text-xs tabular-nums text-muted-foreground">00:28</span
|
||||
><span class="text-sm leading-6"
|
||||
>A assessoria pode saltar diretamente para qualquer frase.</span
|
||||
>
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s3">
|
||||
<div class="mb-4">
|
||||
<p class="text-xs font-semibold text-primary">CENA 03</p>
|
||||
<h2 id="s3" class="mt-1 text-xl font-semibold">Mobile ~390 · lista e detalhe</h2>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-8">
|
||||
<div class="frame w-[390px] overflow-hidden rounded-[28px] border border-border bg-white">
|
||||
<header class="flex h-14 items-center border-b px-4">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Acervo</p>
|
||||
<p class="text-xs text-muted-foreground">Comunicação</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<nav class="grid grid-cols-3 rounded-lg bg-secondary p-1 text-center text-[11px]">
|
||||
<span class="rounded-md px-1 py-2 text-muted-foreground">Câmara</span
|
||||
><span class="rounded-md px-1 py-2 text-muted-foreground">Enviadas</span
|
||||
><span
|
||||
class="rounded-md bg-white px-1 py-2 font-semibold shadow-sm ring-1 ring-border"
|
||||
>Internet</span
|
||||
>
|
||||
</nav>
|
||||
<div class="mt-4 rounded-xl border border-border p-3">
|
||||
<label class="text-sm font-medium">Buscar nas falas</label>
|
||||
<div
|
||||
class="mt-1.5 flex h-11 items-center gap-2 rounded-md border border-input px-3"
|
||||
>
|
||||
<svg class="ico text-muted-foreground"><use href="#search" /></svg
|
||||
><span class="text-sm text-muted-foreground">Assunto, tema ou município…</span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-2 rounded-lg bg-muted p-1 text-center text-sm">
|
||||
<span class="rounded-md bg-white py-2 font-semibold shadow-sm">Termo exato</span
|
||||
><span class="py-2 text-muted-foreground">✦ Por tema</span>
|
||||
</div>
|
||||
<div class="mt-3 flex gap-2 overflow-hidden">
|
||||
<span class="chip">Ano⌄</span><span class="chip">Tema⌄</span
|
||||
><span class="chip">Alcance⌄</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm font-semibold">
|
||||
Resultados <span class="font-normal text-muted-foreground">(contagem)</span>
|
||||
</p>
|
||||
<article class="mt-3 rounded-xl border border-border p-4">
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center rounded-lg bg-stone-800 text-white"
|
||||
>
|
||||
<svg class="size-9"><use href="#play" /></svg
|
||||
><span class="absolute left-2 top-2 rounded bg-black/60 px-2 py-1 text-xs"
|
||||
>Instagram</span
|
||||
>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-muted-foreground">dd/mm/aaaa · —:—</p>
|
||||
<h3 class="mt-1 text-sm font-semibold">Título da fala publicada</h3>
|
||||
<p class="mt-2 text-sm leading-6">
|
||||
… trecho com <mark>termo destacado</mark> na transcrição …
|
||||
</p>
|
||||
<button class="btn btn-p mt-3 w-full">Ver fala</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div class="frame w-[390px] overflow-hidden rounded-[28px] border border-border bg-white">
|
||||
<header class="flex h-14 items-center border-b px-4 text-sm font-semibold">
|
||||
← Detalhe da fala
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div
|
||||
class="relative grid aspect-video place-items-center rounded-lg bg-stone-950 text-white"
|
||||
>
|
||||
<button class="grid size-14 place-items-center rounded-full bg-white text-black">
|
||||
<svg class="size-6"><use href="#play" /></svg></button
|
||||
><span class="absolute left-3 top-3 rounded bg-black/60 px-2 py-1 text-[11px]"
|
||||
>Arquivo espelhado</span
|
||||
>
|
||||
</div>
|
||||
<button class="btn btn-p mt-3 w-full">
|
||||
<svg class="ico"><use href="#download" /></svg>Baixar</button
|
||||
><button class="btn btn-o mt-2 w-full">
|
||||
<svg class="ico"><use href="#external" /></svg>Abrir na origem
|
||||
</button>
|
||||
<div class="mt-5">
|
||||
<span class="rounded-full bg-red-50 px-2 py-1 text-xs font-semibold text-primary"
|
||||
>YouTube</span
|
||||
>
|
||||
<h3 class="mt-2 text-xl font-semibold">Título da fala publicada</h3>
|
||||
<p class="mt-2 text-xs leading-5 text-muted-foreground">
|
||||
YouTube · Canal/autor · dd/mm/aaaa
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-6 text-xs uppercase tracking-wide text-muted-foreground">
|
||||
Transcrição · toque para posicionar
|
||||
</p>
|
||||
<button class="seg active mt-2">
|
||||
<span class="text-xs text-primary">00:12</span
|
||||
><span class="text-sm leading-6"
|
||||
>Trecho ativo da transcrição clicável.</span
|
||||
></button
|
||||
><button class="seg">
|
||||
<span class="text-xs text-muted-foreground">00:28</span
|
||||
><span class="text-sm leading-6">Próximo segmento da fala.</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s4">
|
||||
<div class="mb-4">
|
||||
<p class="text-xs font-semibold text-primary">CENA 04</p>
|
||||
<h2 id="s4" class="mt-1 text-xl font-semibold">
|
||||
Estados críticos · vazio, carregando e sem permissão
|
||||
</h2>
|
||||
</div>
|
||||
<div class="grid gap-4 lg:grid-cols-3">
|
||||
<article
|
||||
class="frame flex min-h-72 flex-col items-center justify-center rounded-xl border border-border bg-white p-8 text-center"
|
||||
>
|
||||
<svg class="size-9 text-muted-foreground"><use href="#search" /></svg>
|
||||
<h3 class="mt-3 font-semibold">Nenhuma fala encontrada</h3>
|
||||
<p class="mt-2 max-w-sm text-sm leading-6 text-muted-foreground">
|
||||
Não encontramos uma fala para esta busca com os filtros atuais. Tente outro termo ou
|
||||
remova um filtro.
|
||||
</p>
|
||||
<button class="btn btn-p mt-5">Limpar busca e filtros</button>
|
||||
</article>
|
||||
<article class="frame min-h-72 rounded-xl border border-border bg-white p-6">
|
||||
<p class="text-sm font-semibold">Carregando falas…</p>
|
||||
<div class="mt-5 space-y-3" aria-label="Carregando">
|
||||
<div class="h-28 animate-pulse rounded-xl bg-muted motion-reduce:animate-none"></div>
|
||||
<div class="h-28 animate-pulse rounded-xl bg-muted motion-reduce:animate-none"></div>
|
||||
</div>
|
||||
</article>
|
||||
<article
|
||||
class="frame flex min-h-72 flex-col items-center justify-center rounded-xl border border-border bg-white p-8 text-center"
|
||||
>
|
||||
<div class="grid size-12 place-items-center rounded-full bg-muted">
|
||||
<svg class="size-5 text-muted-foreground"><use href="#lock" /></svg>
|
||||
</div>
|
||||
<h3 class="mt-3 font-semibold">Você não tem acesso a este acervo</h3>
|
||||
<p class="mt-2 max-w-sm text-sm leading-6 text-muted-foreground">
|
||||
As falas e os arquivos permanecem privados. Entre com uma conta autorizada da
|
||||
comunicação.
|
||||
</p>
|
||||
<button class="btn btn-p mt-5">Voltar para a campanha</button>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
108
docs/plans/acervo-fonte-falas-web.md
Normal file
108
docs/plans/acervo-fonte-falas-web.md
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# C216 — Terceira fonte do acervo: "Falas na internet"
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-23
|
||||
Issue: #1292
|
||||
Priority: P1
|
||||
Impeccable: C — fluxo novo na vertical `/campanha/comunicacao/acervo` (terceira fonte + detalhe próprio)
|
||||
Design UI: docs/plans/acervo-fonte-falas-web-ui-design.html
|
||||
Appetite: ~3–4 dias eng; um outcome verificável — a assessoria alterna para "Falas na internet", busca/filtra como no acervo da Câmara e assiste/baixa a fala encontrada
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O acervo cobre duas coisas: o que a Câmara publica e o que a equipe envia. Falta o que o Solla fala fora disso — YouTube, Instagram, rádios, podcasts — material espalhado, sem transcrição pesquisável e sem lugar único. O C215 descobre e cataloga esse material (download, transcrição e classificação automática); este item é a cara disso no acervo: a terceira fonte "Falas na internet", com a mesma busca e os mesmos filtros do "Falas da Câmara" e um detalhe com player, transcrição clicável, download e link para a origem.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação (`communicator`) — e `coordinator`/`candidate` — na mesa garimpando matéria-prima com prazo curto, ou em campo procurando no celular o que o Solla já disse sobre um tema.
|
||||
- **Job principal:** achar uma fala do Solla publicada na internet sobre um assunto/município, ouvir e baixar o arquivo, e saber de onde ela veio para poder citar.
|
||||
- **Fluxo desejado:** abre o acervo → alterna para "Falas na internet" → busca (termo ou tema) e filtra (ano, tema, alcance, duração, municípios citados) → resultados com trecho destacado e chip da origem → abre o detalhe → player da mídia espelhada no trecho + transcrição clicável por segmento → "Baixar" e "Abrir na origem" (com plataforma, canal/autor e data).
|
||||
- **Anti-goals de produto:** não é editor de vídeo nem publicação; não expõe o acervo publicamente; não cria cadastro de pessoa; não busca _dentro_ da internet (a descoberta é do C215); não mistura as fontes numa lista única.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[acervo /campanha/comunicacao/acervo] → alternador: "Falas da Câmara" | "Gravações enviadas" | "Falas na internet"
|
||||
→ busca com destaque + facetas (ano, tema, alcance, duração, municípios) com paridade da Câmara
|
||||
→ abre o detalhe → player da mídia espelhada + transcrição clicável + "Baixar" + "Abrir na origem"
|
||||
→ [outcome: a fala da internet achada, assistida e baixada, com a origem identificada]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/acervo-fonte-falas-web-ui-design.html` (+ assets em `docs/plans/acervo-fonte-falas-web-ui-design-assets/`) — a produzir pelo designer.
|
||||
- Por que C: fluxo novo na vertical do acervo (terceira fonte, detalhe próprio com player/link externo e estados), mesmo sem rota de topo nova. Design hi-fi obrigatório no gate e fonte de verdade do port.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- O alternador do acervo passa a ter três fontes: "Falas da Câmara" | "Gravações enviadas" | "Falas na internet", com a ativa marcada (`aria-current`) e a troca preservando o contrato de URL da fonte.
|
||||
- "Falas na internet" tem paridade de busca e filtros com "Falas da Câmara" no que a fonte tiver: busca textual com destaque, `mode=termo|tema` (semântica), ano, tema, alcance, duração, municípios citados, contagem e paginação; a lista mostra o trecho com `<mark>` e a contagem.
|
||||
- Detalhe da fala: player da mídia espelhada (arquivo privado servido autenticado), transcrição clicável por segmento (clique posiciona o player), "Baixar" o arquivo, "Abrir na origem" e atribuição visível (plataforma + canal/autor + data).
|
||||
- Lista com chip da origem ("YouTube", "Rádio", …) e capa: thumbnail da origem quando a ingestão a capturar; senão, placeholder.
|
||||
- Estados: vazio (sem resultado), carregando e sem permissão; **sem** estado de processamento por item — a lista mostra o que está pronto e as falhas ficam no relatório da ingestão (C215).
|
||||
- Acesso: mesmo gate fail-closed do acervo (`communicator` + `coordinator`/`candidate`; `advisor`/`leader` negados); a mídia nunca é pública.
|
||||
- Mobile utilizável (a assessoria trabalha em campo).
|
||||
- **Guardrail de direitos de mídia (decisão assumida):** o dono instruiu, verbatim, "Você não deve se preocupar com direitos da mídia, faça o que for necessário para viabilizar a análise e download das mídias que forem encontradas para alimentar esta sessão e para viabilizar as funcionalidades pedidas." Isto supersede a anotação "nunca mídia de terceiro" de C212/C211-FOLLOWUP-OEMBED e vale como premissa do C215; **confirmado no gate (2026-09-24)** — risco assumido.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** N/A — resultados de busca não são métrica nem agregado; não desbloqueiam decisão numérica.
|
||||
- **Decisões desbloqueadas:** N/A — a escolha da persona ("qual fala usar") é qualitativa.
|
||||
- **Forma:** N/A — restrição de produto: nenhum score de relevância/similaridade exposto como número.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Rótulo da fonte:** "Falas na internet" (terceira opção do alternador, ao lado de "Falas da Câmara" e "Gravações enviadas").
|
||||
- **Faceta de origem na lista:** chip com a plataforma ("YouTube", "Rádio", "Instagram", …) — vocabulário exato vem do C215.
|
||||
- **Ações do detalhe:** "Baixar" (arquivo) · "Abrir na origem" (link para o vídeo/post/áudio original) · transcrição clicável.
|
||||
- **Atribuição/identificação da origem:** plataforma + canal/autor + data.
|
||||
- **Parâmetro de URL da fonte:** valor decidido na implementação seguindo o padrão `?source=<valor>` do C199 (sem quebrar o contrato congelado da Câmara, `speechListUrl`). _(adiado à implementação)_
|
||||
- **Estados:** vazio (sem resultado) · carregando · sem permissão; sem estado de processamento por item nesta fase.
|
||||
- **Acesso:** mesmo gate do acervo, fail-closed (`canReadCommunicationCatalog`/`communicationCatalog`); mídia nunca pública.
|
||||
- **Dependência dura:** C215 (ingestão: descoberta, download, transcrição, classificação).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/app/(campaign)/campanha/(app)/comunicacao/acervo/` (página + sub-rota de detalhe), `src/components/campaign/recording/AcervoSourceToggle.tsx` (terceira opção), `src/utilities/recordings/recordingListUrl.ts` (gêmeo do contrato da nova fonte; hoje `parseAcervoSource` é binário fail-closed), `src/components/campaign/speech/` (`SpeechAcervoFilters.tsx`, lista/card, player), `src/utilities/speech/` (`speechListUrl.ts`, `speechPageData.ts`, `speechViewModels.ts`), `src/utilities/privateMedia/privateMediaResponse.ts` (precedente de servir arquivo privado).
|
||||
- **Precedente a olhar:** C199 (segunda fonte + contrato de URL próprio no `?source=enviadas`), C192 (`mode=termo|tema`), `gravacoes/[id]/arquivo`, C200 (faceta nova como recorte na mesma tela).
|
||||
- **Risco de acoplamento:** (1) o contrato da lista da Câmara (`q|mode|year|topic|scope|phase|municipality|duration|page`) está congelado — a fonte nova tem o dele (como o C199 fez), sem renomear params existentes; (2) o detalhe da Câmara é acoplado (VOD da Câmara, `SpeechDetailPlayer`) — a fonte web não tem VOD, o player é da mídia espelhada; (3) o regex genérico de detalhe do chrome (`src/lib/campaignPageChrome.ts`) já mordeu `cortes`/`gravacoes` — escolher rota que não colida; (4) nav de Comunicação pinada (4 itens) e C184/#1106 mexem nela — nada de item de nav novo.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C215 (dura):** sem o catálogo (mídia espelhada + transcrição + classificação) não há o que listar; o desenho pode ser validado antes com dados de amostra.
|
||||
- **C153/C154/C199 (entregues):** base do acervo e alternador de fonte.
|
||||
- **C184/#1106 (soft):** conflito de nav/chrome — serializar.
|
||||
- **C217/C218 (futuros):** cortes e skill de atualização; não bloqueiam.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Ingestão/descoberta, download, transcrição e classificação (C215).
|
||||
- Cortes e links compartilháveis (C217); skill de atualização (C218).
|
||||
- Paridade das gravações (C219).
|
||||
- Diarização, edição de transcrição, publicação externa.
|
||||
- Merge das fontes numa busca global única; item novo de nav.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Merge multi-fonte numa lista única.** Se alguém "só completar": ranking entre fontes, dedupe, busca global. **Corte neste item:** alternador dentro do acervo.
|
||||
- **Segundo cadastro de pessoa.** Se alguém "só completar": transformar canal/autor em `Contact`. **Corte neste item:** atribuição é texto de origem.
|
||||
- **Editor de vídeo.** Se alguém "só completar": cortar/legendar/remixar. **Corte neste item:** player + transcrição + download; corte é o C217.
|
||||
- **Dashboard de vaidade.** Se alguém "só completar": views, alcance, KPIs. **Corte neste item:** sem métrica de audiência.
|
||||
- **Espelhar tudo em qualidade máxima.** Se alguém "só completar": baixar/guardar versão original de tudo. **Corte neste item:** o que o C215 catalogar; nada de biblioteca de masters.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **A fonte entra no mesmo alternador ou em rota separada?** **Opções:** A) terceira opção no mesmo alternador (recomendado) | B) rota separada. **Recomendação:** A — é o mesmo job no mesmo lugar; rota separada fragmenta o acervo. _(confirmado no gate, 2026-09-24)_
|
||||
- **Capa do card?** **Opções:** A) thumbnail da origem quando a ingestão a capturar; senão placeholder | B) sem capa. **Recomendação:** A — a lista da Câmara já usa imagem e a leitura fica mais rápida; o C215 fornece a thumb. _(confirmado no gate, 2026-09-24)_
|
||||
- **Mostrar a origem já na lista?** **Opções:** A) sim, chip "YouTube"/"Rádio"/… | B) só no detalhe. **Recomendação:** A — a assessoria escolhe a peça já sabendo de onde a fala veio. _(confirmado no gate, 2026-09-24)_
|
||||
- **Item sem transcrição (ASR falhou)?** **Opções:** A) fora da lista, só no relatório da ingestão | B) listado com aviso e só link de origem. **Recomendação:** A nesta fase — a fonte web nasce para busca; item sem texto só ruído. _(confirmado no gate, 2026-09-24)_
|
||||
- **Valor do parâmetro de URL da fonte?** **Opções:** A) decidido na implementação seguindo o padrão `?source=<valor>` do C199 | B) travar agora. **Recomendação:** A — mesma convenção do alternador atual. _(adiado à implementação)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue — (após `pnpm agent:register`)
|
||||
- Design UI (gate): `docs/plans/acervo-fonte-falas-web-ui-design.html` (+ assets em `docs/plans/acervo-fonte-falas-web-ui-design-assets/`)
|
||||
- Planos irmãos: `docs/plans/acervo-gravacoes-enviadas.md` (C199, precedente direto de fonte) · `docs/plans/acervo-videos-comunicacao.md` (C154) · `docs/plans/acervo-separacao-por-pessoa.md` (C200, formato de plano C de acervo) · `docs/plans/acervo-busca-semantica.md` (C192)
|
||||
- Arquivos-pista: `src/app/(campaign)/campanha/(app)/comunicacao/acervo/page.tsx` · `src/components/campaign/recording/AcervoSourceToggle.tsx` · `src/utilities/recordings/recordingListUrl.ts` · `src/components/campaign/speech/` · `src/utilities/speech/speechListUrl.ts` · `src/utilities/speech/speechPageData.ts` · `src/utilities/privateMedia/privateMediaResponse.ts` · `src/lib/campaignPageChrome.ts`
|
||||
- `AGENTS.md` — RBAC de `/campanha`, acervo interno e leader lockdown
|
||||
512
docs/plans/acervo-paridade-busca-filtros-ui-design.html
Normal file
512
docs/plans/acervo-paridade-busca-filtros-ui-design.html
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
<!doctype html>
|
||||
<html lang="pt-BR" data-theme="campaign">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>Design — C219 · Paridade de busca e filtros</title>
|
||||
<style type="text/tailwindcss">
|
||||
@theme inline {
|
||||
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--radius-md: 0.375rem;
|
||||
--radius-lg: 0.5rem;
|
||||
--radius-xl: 0.75rem;
|
||||
}
|
||||
[data-theme='campaign'] {
|
||||
--background: #fff;
|
||||
--foreground: #1c1917;
|
||||
--card: #fff;
|
||||
--primary: #c51414;
|
||||
--primary-foreground: #fff;
|
||||
--secondary: #f5f5f4;
|
||||
--muted: #f4f4f5;
|
||||
--muted-foreground: #6b7280;
|
||||
--border: rgb(28 25 23 / 12%);
|
||||
--input: #d6d3d1;
|
||||
--ring: #c51414;
|
||||
--destructive: #b42318;
|
||||
--sidebar: #fafaf9;
|
||||
--sidebar-border: rgb(28 25 23 / 12%);
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.grid-bg {
|
||||
background-image: radial-gradient(rgb(28 25 23 / 8%) 0.7px, transparent 0.7px);
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
.frame {
|
||||
box-shadow: 0 18px 54px rgb(28 25 23 / 10%);
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
min-height: 2.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.5rem 0.8rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-p {
|
||||
background: #c51414;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-o {
|
||||
border: 1px solid #d6d3d1;
|
||||
background: #fff;
|
||||
}
|
||||
.btn-g {
|
||||
background: transparent;
|
||||
}
|
||||
.ico {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
stroke-width: 2;
|
||||
}
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
min-height: 2rem;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
padding: 0.25rem 0.7rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chip-on {
|
||||
border-color: rgb(197 20 20 / 28%);
|
||||
background: rgb(197 20 20 / 7%);
|
||||
color: #a20f0f;
|
||||
}
|
||||
mark {
|
||||
border-radius: 0.2rem;
|
||||
background: #fef08a;
|
||||
padding: 0 0.12rem;
|
||||
color: inherit;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="grid-bg min-h-screen bg-[#efeeec] text-foreground antialiased">
|
||||
<svg class="absolute size-0 overflow-hidden" aria-hidden="true">
|
||||
<symbol id="search" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.3-4.3" />
|
||||
</symbol>
|
||||
<symbol id="video" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<rect x="3" y="5" width="18" height="14" rx="2" />
|
||||
<path d="m10 9 5 3-5 3z" />
|
||||
</symbol>
|
||||
<symbol id="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m9 18 6-6-6-6" />
|
||||
</symbol>
|
||||
<symbol id="down" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</symbol>
|
||||
<symbol id="x" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m18 6-12 12M6 6l12 12" />
|
||||
</symbol>
|
||||
<symbol id="user" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<circle cx="12" cy="8" r="4" />
|
||||
<path d="M4 21a8 8 0 0 1 16 0" />
|
||||
</symbol>
|
||||
<symbol id="lock" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<rect x="3" y="11" width="18" height="10" rx="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</symbol>
|
||||
<symbol id="spark" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="m12 3-1.7 4.6L6 9.3l4.3 1.7 1.7 4.7 1.7-4.7L18 9.3l-4.3-1.7L12 3Z" />
|
||||
<path d="m19 14-.9 2.1L16 17l2.1.9L19 20l.9-2.1L22 17l-2.1-.9L19 14Z" />
|
||||
</symbol>
|
||||
</svg>
|
||||
<main class="mx-auto flex max-w-[1480px] flex-col gap-14 px-4 py-10 md:px-8">
|
||||
<header class="max-w-3xl">
|
||||
<div class="flex gap-2">
|
||||
<span class="rounded-full bg-primary px-2.5 py-1 text-xs font-semibold text-white"
|
||||
>C219 · gate hi-fi</span
|
||||
><span class="rounded-full border border-border bg-white px-2.5 py-1 text-xs"
|
||||
>Campanha · Comunicação</span
|
||||
>
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight">
|
||||
Mesmos gestos para encontrar gravações
|
||||
</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
“Gravações enviadas” ganha busca por termo ou tema, facetas, chips ativos, contagem e
|
||||
ordenação — Pessoa continua exclusiva desta fonte.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<section aria-labelledby="s1">
|
||||
<p class="text-xs font-semibold text-primary">CENA 01</p>
|
||||
<h2 id="s1" class="mt-1 text-xl font-semibold">
|
||||
Desktop ~1280 · filtros completos e resultados
|
||||
</h2>
|
||||
<div class="frame mt-4 overflow-x-auto rounded-xl border border-border bg-white">
|
||||
<div class="flex min-h-[1040px] min-w-[1180px]">
|
||||
<aside
|
||||
class="flex w-60 shrink-0 flex-col border-r border-sidebar-border bg-sidebar p-3"
|
||||
>
|
||||
<div class="flex h-12 items-center gap-2 px-2">
|
||||
<div
|
||||
class="grid size-8 place-items-center rounded-lg bg-primary font-bold text-white"
|
||||
>
|
||||
S
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Solla</p>
|
||||
<p class="text-xs text-muted-foreground">Campanha</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="mt-4 space-y-1 text-sm">
|
||||
<div class="rounded-md px-3 py-2 text-muted-foreground">Início</div>
|
||||
<div class="rounded-md px-3 py-2 text-muted-foreground">Municípios</div>
|
||||
<div class="rounded-lg bg-secondary p-1">
|
||||
<div class="px-2 py-2 font-medium">Comunicação</div>
|
||||
<div class="ml-4 border-l border-border pl-3">
|
||||
<div
|
||||
class="rounded-md bg-white px-2 py-2 font-medium text-primary ring-1 ring-border"
|
||||
>
|
||||
Acervo de falas
|
||||
</div>
|
||||
<div class="px-2 py-2 text-muted-foreground">Biblioteca de cortes</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<p class="mt-auto border-t border-border px-2 pt-3 text-xs text-muted-foreground">
|
||||
Assessoria · Comunicação
|
||||
</p>
|
||||
</aside>
|
||||
<div class="min-w-0 flex-1">
|
||||
<header class="flex h-16 items-center border-b border-border px-8">
|
||||
<div>
|
||||
<p class="font-semibold">Acervo de falas</p>
|
||||
<p class="text-xs text-muted-foreground">Comunicação</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mx-auto max-w-[940px] px-8 py-7">
|
||||
<div class="flex items-end justify-between">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">Acervo de falas</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
Busque nas falas da Câmara ou nas gravações da equipe.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-o">Enviar gravação</button>
|
||||
</div>
|
||||
<nav class="mt-5 inline-flex rounded-lg bg-secondary p-1 text-sm">
|
||||
<span class="rounded-md px-3 py-2 text-muted-foreground">Falas da Câmara</span
|
||||
><span
|
||||
aria-current="page"
|
||||
class="rounded-md bg-white px-3 py-2 font-medium shadow-sm ring-1 ring-border"
|
||||
>Gravações enviadas</span
|
||||
><span class="rounded-md px-3 py-2 text-muted-foreground">Falas na internet</span>
|
||||
</nav>
|
||||
<form class="mt-5 rounded-xl border border-border bg-card p-4 shadow-sm">
|
||||
<label class="text-sm font-medium">Buscar nas gravações enviadas</label>
|
||||
<div
|
||||
class="mt-1.5 flex min-h-11 items-center gap-2 rounded-md border border-ring px-3 ring-3 ring-ring/15"
|
||||
>
|
||||
<svg class="ico text-muted-foreground"><use href="#search" /></svg
|
||||
><input
|
||||
class="min-w-0 flex-1 bg-transparent text-sm outline-none"
|
||||
value="saúde no município"
|
||||
readonly
|
||||
/><button class="grid size-8 place-items-center text-muted-foreground">
|
||||
<svg class="ico"><use href="#x" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 flex flex-wrap items-center justify-between gap-3 border-t border-border pt-3"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs font-medium text-muted-foreground">Buscar por</span>
|
||||
<div class="inline-flex rounded-lg bg-muted p-1 text-sm">
|
||||
<button class="rounded-md px-3 py-2 text-muted-foreground">
|
||||
Termo exato</button
|
||||
><button
|
||||
class="rounded-md bg-white px-3 py-2 font-semibold text-primary shadow-sm ring-1 ring-border"
|
||||
>
|
||||
<span class="inline-flex items-center gap-1.5"
|
||||
><svg class="size-3.5"><use href="#spark" /></svg>Por tema</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
Encontra gravações relacionadas pelo sentido.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2" aria-label="Filtros">
|
||||
<button class="chip">
|
||||
Ano<svg class="size-3"><use href="#down" /></svg></button
|
||||
><button class="chip chip-on">
|
||||
Tema: Saúde<svg class="size-3"><use href="#down" /></svg></button
|
||||
><button class="chip">
|
||||
Alcance<svg class="size-3"><use href="#down" /></svg></button
|
||||
><button class="chip chip-on">
|
||||
Município: selecionado<svg class="size-3"><use href="#down" /></svg></button
|
||||
><button class="chip">
|
||||
Duração<svg class="size-3"><use href="#down" /></svg></button
|
||||
><button class="chip chip-on">
|
||||
<svg class="size-3"><use href="#user" /></svg>Pessoa: Nome da pessoa<svg
|
||||
class="size-3"
|
||||
>
|
||||
<use href="#down" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap items-center gap-2 border-t border-border pt-3">
|
||||
<span class="text-xs font-medium text-muted-foreground">Ativos:</span
|
||||
><button class="chip chip-on">
|
||||
Tema: Saúde <svg class="size-3"><use href="#x" /></svg></button
|
||||
><button class="chip chip-on">
|
||||
Município: selecionado <svg class="size-3"><use href="#x" /></svg></button
|
||||
><button class="chip chip-on">
|
||||
Pessoa: Nome da pessoa <svg class="size-3"><use href="#x" /></svg></button
|
||||
><button class="ml-auto text-xs font-semibold text-primary">Limpar tudo</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-5 flex items-center justify-between gap-3">
|
||||
<p class="text-sm font-semibold">
|
||||
Gravações encontradas
|
||||
<span class="font-normal text-muted-foreground">(contagem)</span>
|
||||
</p>
|
||||
<label class="flex items-center gap-2 text-xs text-muted-foreground"
|
||||
>Ordenar por<select
|
||||
class="rounded-md border border-input bg-white px-3 py-2 text-sm text-foreground"
|
||||
>
|
||||
<option>Mais recentes</option>
|
||||
<option>Duração (maior)</option>
|
||||
<option>Duração (menor)</option>
|
||||
</select></label
|
||||
>
|
||||
</div>
|
||||
<div class="mt-3 space-y-3">
|
||||
<article
|
||||
class="grid grid-cols-[112px_minmax(0,1fr)_auto] gap-4 rounded-xl border border-border p-4"
|
||||
>
|
||||
<div
|
||||
class="grid aspect-video place-items-center rounded-lg bg-stone-200 text-stone-500"
|
||||
>
|
||||
<svg class="size-7"><use href="#video" /></svg>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<h4 class="truncate text-sm font-semibold">Título da gravação enviada</h4>
|
||||
<span
|
||||
class="rounded-full bg-green-100 px-2 py-1 text-xs font-semibold text-green-800"
|
||||
>Pronto</span
|
||||
>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">dd/mm/aaaa · —:—</p>
|
||||
<p class="mt-2 text-sm leading-6">
|
||||
… trecho em que <mark>saúde</mark> aparece na transcrição da gravação …
|
||||
</p>
|
||||
<div class="mt-2 flex flex-wrap gap-1.5">
|
||||
<span class="rounded-md bg-muted px-2 py-1 text-xs">Saúde</span
|
||||
><span class="rounded-md bg-muted px-2 py-1 text-xs">Nome da pessoa</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-p self-center">
|
||||
Abrir<svg class="ico"><use href="#chev" /></svg>
|
||||
</button>
|
||||
</article>
|
||||
<article
|
||||
class="grid grid-cols-[112px_minmax(0,1fr)_auto] gap-4 rounded-xl border border-border p-4"
|
||||
>
|
||||
<div
|
||||
class="grid aspect-video place-items-center rounded-lg bg-stone-200 text-stone-500"
|
||||
>
|
||||
<svg class="size-7"><use href="#video" /></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h4 class="text-sm font-semibold">Outra gravação enviada</h4>
|
||||
<span
|
||||
class="rounded-full bg-green-100 px-2 py-1 text-xs font-semibold text-green-800"
|
||||
>Pronto</span
|
||||
>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">dd/mm/aaaa · —:—</p>
|
||||
<p class="mt-2 text-sm leading-6">
|
||||
… resultado relacionado ao <mark>tema buscado</mark>, com contexto
|
||||
suficiente para decidir …
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-o self-center">
|
||||
Abrir<svg class="ico"><use href="#chev" /></svg>
|
||||
</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s2">
|
||||
<p class="text-xs font-semibold text-primary">CENA 02</p>
|
||||
<h2 id="s2" class="mt-1 text-xl font-semibold">Mobile ~390 · barra e lista</h2>
|
||||
<div
|
||||
class="frame mt-4 w-[390px] overflow-hidden rounded-[28px] border border-border bg-white"
|
||||
>
|
||||
<header class="flex h-14 items-center border-b px-4">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Acervo</p>
|
||||
<p class="text-xs text-muted-foreground">Comunicação</p>
|
||||
</div>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<nav class="grid grid-cols-3 rounded-lg bg-secondary p-1 text-center text-[11px]">
|
||||
<span class="rounded-md px-1 py-2 text-muted-foreground">Câmara</span
|
||||
><span
|
||||
class="rounded-md bg-white px-1 py-2 font-semibold shadow-sm ring-1 ring-border"
|
||||
>Enviadas</span
|
||||
><span class="rounded-md px-1 py-2 text-muted-foreground">Internet</span>
|
||||
</nav>
|
||||
<form class="mt-4 rounded-xl border border-border p-3">
|
||||
<label class="text-sm font-medium">Buscar nas gravações</label>
|
||||
<div class="mt-1.5 flex h-11 items-center gap-2 rounded-md border border-input px-3">
|
||||
<svg class="ico text-muted-foreground"><use href="#search" /></svg
|
||||
><span class="text-sm text-muted-foreground">Palavra, trecho ou tema…</span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-2 rounded-lg bg-muted p-1 text-center text-sm">
|
||||
<span class="rounded-md bg-white py-2 font-semibold shadow-sm">Termo exato</span
|
||||
><span class="py-2 text-muted-foreground">✦ Por tema</span>
|
||||
</div>
|
||||
<div class="mt-3 flex gap-2 overflow-hidden">
|
||||
<span class="chip">Ano⌄</span><span class="chip chip-on">Tema: Saúde⌄</span
|
||||
><span class="chip">Alcance⌄</span>
|
||||
</div>
|
||||
<div class="mt-2 flex gap-2 overflow-hidden">
|
||||
<span class="chip">Município⌄</span><span class="chip">Duração⌄</span
|
||||
><span class="chip chip-on">Pessoa⌄</span>
|
||||
</div>
|
||||
<div class="mt-3 flex gap-2 overflow-hidden border-t border-border pt-3">
|
||||
<span class="chip chip-on">Saúde ×</span
|
||||
><span class="chip chip-on">Pessoa: Nome… ×</span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<p class="text-sm font-semibold">
|
||||
Resultados <span class="font-normal text-muted-foreground">(contagem)</span>
|
||||
</p>
|
||||
<select class="max-w-36 rounded-md border border-input bg-white px-2 py-2 text-xs">
|
||||
<option>Mais recentes</option>
|
||||
</select>
|
||||
</div>
|
||||
<article class="mt-3 rounded-xl border border-border p-4">
|
||||
<div
|
||||
class="grid aspect-video place-items-center rounded-lg bg-stone-200 text-stone-500"
|
||||
>
|
||||
<svg class="size-8"><use href="#video" /></svg>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<h3 class="min-w-0 flex-1 truncate text-sm font-semibold">Título da gravação</h3>
|
||||
<span
|
||||
class="rounded-full bg-green-100 px-2 py-1 text-xs font-semibold text-green-800"
|
||||
>Pronto</span
|
||||
>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">dd/mm/aaaa · —:—</p>
|
||||
<p class="mt-2 text-sm leading-6">
|
||||
… trecho com <mark>termo destacado</mark> na transcrição …
|
||||
</p>
|
||||
<button class="btn btn-p mt-3 w-full">Abrir gravação</button>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s3">
|
||||
<p class="text-xs font-semibold text-primary">CENA 03</p>
|
||||
<h2 id="s3" class="mt-1 text-xl font-semibold">Vazio honesto · limpar ou trocar o modo</h2>
|
||||
<div
|
||||
class="frame mt-4 flex min-h-[420px] items-center justify-center rounded-xl border border-border bg-white p-8"
|
||||
>
|
||||
<div class="max-w-lg text-center">
|
||||
<div class="mx-auto grid size-14 place-items-center rounded-full bg-muted">
|
||||
<svg class="size-6 text-muted-foreground"><use href="#search" /></svg>
|
||||
</div>
|
||||
<h3 class="mt-4 text-lg font-semibold">Nenhuma gravação encontrada para este tema</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
Não encontramos uma gravação que corresponda ao sentido desta busca com os filtros
|
||||
atuais. Não vamos preencher a lista com resultados pouco relacionados.
|
||||
</p>
|
||||
<div class="mt-5 flex flex-wrap justify-center gap-2">
|
||||
<button class="btn btn-p">Limpar filtros</button
|
||||
><button class="btn btn-o">Trocar para termo exato</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s4">
|
||||
<p class="text-xs font-semibold text-primary">CENA 04</p>
|
||||
<h2 id="s4" class="mt-1 text-xl font-semibold">Sem permissão · fail-closed</h2>
|
||||
<div
|
||||
class="frame mt-4 flex min-h-[420px] items-center justify-center rounded-xl border border-border bg-white p-8"
|
||||
>
|
||||
<div class="max-w-md text-center">
|
||||
<div class="mx-auto grid size-14 place-items-center rounded-full bg-muted">
|
||||
<svg class="size-6 text-muted-foreground"><use href="#lock" /></svg>
|
||||
</div>
|
||||
<h3 class="mt-4 text-lg font-semibold">Você não tem acesso a este acervo</h3>
|
||||
<p class="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
As gravações, transcrições e filtros permanecem privados. Entre com uma conta
|
||||
autorizada da comunicação.
|
||||
</p>
|
||||
<button class="btn btn-p mt-5">Voltar para a campanha</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="s5">
|
||||
<p class="text-xs font-semibold text-primary">CENA 05</p>
|
||||
<h2 id="s5" class="mt-1 text-xl font-semibold">Carregando · contexto preservado</h2>
|
||||
<div class="frame mt-4 rounded-xl border border-border bg-white p-6">
|
||||
<div class="rounded-xl border border-border p-4">
|
||||
<div class="h-11 animate-pulse rounded-lg bg-muted"></div>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<div class="h-8 w-20 animate-pulse rounded-full bg-muted"></div>
|
||||
<div class="h-8 w-24 animate-pulse rounded-full bg-muted"></div>
|
||||
<div class="h-8 w-28 animate-pulse rounded-full bg-muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 text-sm font-medium">Atualizando resultados…</p>
|
||||
<div class="mt-3 space-y-3">
|
||||
<div class="h-32 animate-pulse rounded-xl bg-muted"></div>
|
||||
<div class="h-32 animate-pulse rounded-xl bg-muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
118
docs/plans/acervo-paridade-busca-filtros.md
Normal file
118
docs/plans/acervo-paridade-busca-filtros.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# C219 — Paridade de busca e filtros entre as fontes do acervo
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-23
|
||||
Issue: #1295
|
||||
Priority: P2
|
||||
Impeccable: B — encaixe na tela existente do acervo (barra de filtros e facetas da fonte de gravações)
|
||||
Design UI: docs/plans/acervo-paridade-busca-filtros-ui-design.html
|
||||
Appetite: ~2–3 dias eng; a assessoria filtra "Gravações enviadas" por ano, duração, tema, alcance e município citado, e busca por tema como na Câmara, com o mesmo comportamento de filtros
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O dono percebeu o desnível: "o 'Gravações enviadas' não está hoje com todas as funcionalidades de pesquisa e filtro que temos para 'Falas da Câmara'". As duas fontes vivem no mesmo acervo, mas a experiência é desigual — a Câmara tem busca por termo/tema, ano, duração e facetas de conteúdo; as gravações têm só busca textual e pessoa. Quem garimpa uma plenária enviada trabalha com menos instrumentos do que quem garimpa um discurso de plenário.
|
||||
|
||||
Este item fecha as lacunas da fonte de gravações e deixa registrado o contrato que **toda** fonte do acervo deve cumprir — a fonte nova ("Falas na internet", C216) nasce com ele. Paridade é do conjunto de capacidades, não de facetas idênticas: "Pessoa" continua exclusiva das gravações.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** assessoria de comunicação (`communicator`) — e `coordinator`/`candidate` — na mesa, sob prazo curto, garimpando fala em plenárias/debates enviados pela equipe; busca também acontece em campo.
|
||||
- **Job principal:** achar a gravação/trecho sobre um assunto, tema, ano ou município com os mesmos gestos que já usa nas falas da Câmara.
|
||||
- **Fluxo desejado:**
|
||||
1. Abre o acervo e alterna para "Gravações enviadas".
|
||||
2. Digita o assunto e escolhe termo exato ou por tema (como na Câmara).
|
||||
3. Restringe por ano, duração, tema, alcance e município citado; combina filtros e vê a contagem.
|
||||
4. Vê os chips dos filtros ativos, limpa um ou todos, reordena.
|
||||
5. Abre a gravação e assiste/baixa (fluxo atual intocado).
|
||||
6. Vazio honesto quando nada serve; gravação sem transcrição segue visível, fora dos filtros de conteúdo.
|
||||
- **Anti-goals de produto:** não unifica as fontes numa lista única; não vira curadoria de taxonomia; não cria cadastro de pessoa; não redesenha a barra da Câmara; não classifica conteúdo de terceiros (C211 é outro dono).
|
||||
|
||||
### Esboço de fluxo (B)
|
||||
|
||||
```text
|
||||
[fonte "Gravações enviadas"] → busca (termo | tema) + ano + duração + tema + alcance + município
|
||||
→ lista com trecho destacado, contagem, chips ativos e ordenação
|
||||
→ abre a gravação → player/transcrição/baixar (como hoje)
|
||||
→ vazio honesto: limpar filtros | trocar modo
|
||||
[outcome: achar a gravação certa com os mesmos gestos da Câmara]
|
||||
```
|
||||
|
||||
### Design UI (B)
|
||||
|
||||
- Design UI (gate): `docs/plans/acervo-paridade-busca-filtros-ui-design.html`
|
||||
- Por que B: as capacidades já existem na tela; o item estende a barra de filtros e a lista da fonte de gravações na linguagem da Câmara, sem rota nova. Design hi-fi obrigatório no gate e fonte de verdade do port.
|
||||
|
||||
```text
|
||||
[Câmara] busca [termo|tema] · Ano · Tema · Alcance · Município · Fase · Duração
|
||||
[Enviadas] busca [termo|tema] · Ano · Tema · Alcance · Município · Duração · Pessoa
|
||||
```
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Contrato de paridade do acervo cumprido por toda fonte, no que for aplicável: busca textual com destaque; contagem; paginação; busca semântica `mode=termo|tema`; filtro por data/ano; filtro por duração; facetas de conteúdo quando houver classificação; ordenação; estados vazio/carregando/sem permissão.
|
||||
- "Gravações enviadas" ganha: ano (data da gravação), duração, busca por tema (`mode=tema`) e facetas Tema/Alcance/Município citado — derivadas por classificação automática com proveniência (`classifiedBy`), sobre a transcrição já existente.
|
||||
- Mesma experiência de filtros da Câmara: chips dos ativos, limpar, contagem, paginação preservada no deep link.
|
||||
- Classificação é automática e revisável; nunca inventa — falha degrada para o que existir e é sinalizada; combina com os filtros existentes (`q`, Pessoa) sem regressão.
|
||||
- **Guardrails:** sem segundo cadastro de pessoa ("Pessoa" continua rótulo, C200); gate fail-closed do acervo intocado; acervo interno; sem score/vaidade.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** N/A — resultados e facetas de busca são recorte editorial, não métrica nem agregado.
|
||||
- **Decisões desbloqueadas:** N/A — a escolha ("qual gravação/fala serve") é qualitativa.
|
||||
- **Forma:** N/A — sem superfície de dados; restrição de produto: proveniência é rótulo auditável, nunca score numérico na UI.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Contrato de paridade (capacidades):** busca textual com destaque do termo · contagem de resultados · paginação · busca semântica `mode=termo|tema` · filtro por data/ano · filtro por duração · facetas de conteúdo (tema, alcance, municípios citados) quando a fonte tiver classificação · ordenação · estados vazio/carregando/sem permissão.
|
||||
- **Faceta exclusiva não quebra paridade:** "Pessoa" permanece só em "Gravações enviadas"; o contrato é do conjunto de capacidades, não de facetas idênticas.
|
||||
- **Rótulos novos nas gravações:** "Ano" · "Duração" · "Tema" · "Alcance" · "Município citado"; buckets de duração idênticos à Câmara ("Até 2 min" · "2 a 5 min" · "Mais de 5 min" · "Sem duração").
|
||||
- **Ano:** derivado da data da gravação já informada no envio (`recordedAt`) — nunca um segundo campo digitado.
|
||||
- **Proveniência:** mesmos valores da Câmara — `gazetteer` | `llm` | `manual` (campo `classifiedBy`) na fonte de gravações.
|
||||
- **Modo de busca:** `mode=termo` é o default (nunca serializado; deep-links atuais intactos) e `mode=tema` é explícito, como na Câmara.
|
||||
- **Ordenação (rótulos):** "Mais recentes" (default) · "Duração (maior)" · "Duração (menor)".
|
||||
- **Papéis/gate:** mesmo gate de leitura do acervo (`communicator` + `coordinator`/`candidate`; `advisor`/`leader` negados, fail-closed).
|
||||
- **URL:** a fonte mantém `?source=enviadas` e ganha os novos params no seu contrato; o contrato da Câmara não muda.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/utilities/recordings/` (`recordingListUrl.ts`, `recordingListFilters.ts`, `recordingPageData.ts`, `recordingViewModels.ts`), `src/components/campaign/recording/` (barra de filtros e lista), `src/collections/Recording.ts` (facets + proveniência), `src/utilities/speech/speechClassifier.ts` (dono único — reusar, não duplicar), `src/utilities/ai/expandSpeechSearchTheme.ts` (ponte de tema por corpus), `src/lib/speechFacets.ts` (taxonomia única).
|
||||
- **Precedente a olhar:** `SpeechAcervoFilters.tsx` + `speechListUrl.ts`/`speechListFilters.ts` (C154/C192) para a experiência e o contrato; C200 (Pessoa como rótulo derivado); import da Câmara (`scripts/import-camara-speeches.mjs`) para o padrão de classificação com proveniência.
|
||||
- **Risco de acoplamento:** o contrato de `source=enviadas` sustenta deep-links (`q|person|page`) — não quebrar; classificar a transcrição existente sem tocar diarização/rótulos (C200); um só dono de classificador e taxonomia; gate fail-closed intocado.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **Nenhuma dura.**
|
||||
- **Soft (serialize): C216** — a fonte nova nasce com o contrato; se ambos estiverem em voo, serializar nos componentes de filtros do acervo (mesmos arquivos).
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Fonte web "Falas na internet" (C215/C216) — o contrato vale; a implementação é de lá.
|
||||
- Cortes/clipes (C217) e a skill do acervo (C218).
|
||||
- Diarização e edição de transcrição (C200 / itens próprios).
|
||||
- Merge das fontes numa lista única / busca global.
|
||||
- Classificação de conteúdo de terceiros na Central de Conteúdos (C211).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Unificar as fontes numa busca só.** Se alguém "só completar": lista Câmara+gravações+web, paginação multi-coleção, ranking entre fontes. **Corte neste item:** cada fonte mantém sua lista; paridade é de capacidades.
|
||||
- **Redesenhar a barra de filtros da Câmara.** Se alguém "só completar": shell novo e migração de todos os filtros. **Corte:** a Câmara é a referência; muda-se a fonte de gravações.
|
||||
- **Classificar por segmento/falante.** Se alguém "só completar": tema por trecho, timeline por pessoa. **Corte:** classificação no nível da gravação, como a fala é a unidade da Câmara.
|
||||
- **Segundo cadastro de pessoa.** **Corte:** "Pessoa" continua rótulo (C200).
|
||||
- **Dashboard de vaidade.** **Corte:** nada de métrica de busca/uso.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Classificar as gravações para ter tema/alcance/municípios?** **Opções:** A) sim, classificação automática com proveniência | B) não, só filtros genéricos (ano/duração/semântica). **Recomendação:** A — sem classificação não há paridade real de facetas; a proveniência mantém tudo auditável. _(confirmado no gate, 2026-09-24)_
|
||||
- **Ordenação?** **Opções:** A) data (mais recente primeiro) + duração (maior/menor) | B) só data. **Recomendação:** A — achar o trecho longo/curto é gesto recorrente de quem monta peça. _(confirmado no gate, 2026-09-24)_
|
||||
- **Busca semântica nas gravações?** **Opções:** A) estender a ponte de tema ao corpus de gravações | B) só busca literal. **Recomendação:** A — é a paridade pedida; a ponte já existe e degrada sozinha. _(confirmado no gate, 2026-09-24)_
|
||||
- **Gravação sem transcrição (processando/falhou)?** **Opções:** A) fica fora dos filtros de conteúdo e visível como hoje | B) esconder da lista. **Recomendação:** A — estado honesto (como no C199); esconder faz o arquivo sumir sem explicação. _(confirmado no gate, 2026-09-24)_
|
||||
- **Ano da gravação?** **Opções:** A) derivar da data já informada | B) criar campo próprio. **Recomendação:** A — uma fonte de verdade da data; campo próprio criaria dupla digitação. _(confirmado no gate, 2026-09-24)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue: — (após `pnpm agent:register`)
|
||||
- Design UI (gate): `docs/plans/acervo-paridade-busca-filtros-ui-design.html`
|
||||
- Planos irmãos: `docs/plans/acervo-videos-comunicacao.md` (C154) · `docs/plans/acervo-gravacoes-enviadas.md` (C199) · `docs/plans/acervo-separacao-por-pessoa.md` (C200) · `docs/plans/acervo-busca-semantica.md` (C192)
|
||||
- Arquivos-pista: `src/utilities/recordings/recordingListUrl.ts` · `recordingListFilters.ts` · `recordingPageData.ts` · `src/components/campaign/recording/` · `src/components/campaign/speech/SpeechAcervoFilters.tsx` · `src/utilities/speech/speechClassifier.ts` · `src/utilities/ai/expandSpeechSearchTheme.ts` · `src/lib/speechFacets.ts` · `src/collections/Recording.ts`
|
||||
- `AGENTS.md` — RBAC de `/campanha`, acervo interno e leader lockdown
|
||||
87
docs/plans/skill-catalogo-falas-web.md
Normal file
87
docs/plans/skill-catalogo-falas-web.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# C218 — Skill catalogo-falas-web: atualização incremental do catálogo
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-23
|
||||
Issue: #1294
|
||||
Priority: P2
|
||||
Impeccable: A — N/A (skill/docs, sem UI)
|
||||
Design UI: N/A — sem UI
|
||||
Appetite: ~1–2 dias eng; um outcome verificável — rodar a skill atualiza o catálogo desde a última execução bem-sucedida e devolve o recibo, sem duplicar
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O catálogo de falas já tem como ingerir mídia (C215), mas alguém precisa descobrir o que apareceu novo na internet desde a última rodada — e hoje isso é trabalho manual, sem registro de onde parou. Esta skill transforma a atualização numa operação repetível: lê a data da última execução, faz a descoberta web do que surgiu desde então, monta o lote de achados e chama a esteira de ingestão do C215, devolvendo um recibo curto. É a resposta à ideia do dono (2026-09-23): "atualizar essa sessão fazendo um novo scraping a partir da data que foi feito o último".
|
||||
|
||||
Sem isso, cada atualização recomeça do zero (ou do que a memória de quem opera lembrar) e o catálogo envelhece entre entregas da comunicação.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** agente/coordenação técnica (não a assessoria); roda na workstation/worktree, como as skills de conteúdo dos dossiês.
|
||||
- **Job principal:** dar um comando e ter o catálogo atualizado desde a última execução, sabendo o que entrou, o que foi ignorado e o que falhou.
|
||||
- **Fluxo desejado:** `/catalogo-falas-web` → a skill lê a data da última execução (primeira rodada: varredura inicial completa) → descobre o que apareceu desde então → cura falsos positivos → entrega o lote à ingestão (C215) → imprime o recibo (período, achados, novos, ignorados, falhas, custo/tempo).
|
||||
- **Anti-goals de produto:** não é serviço de monitoramento; não é tela (sem UI); não decide faceta manualmente (transcrição/classificação é do C215); não promete cobertura exaustiva da web.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Rodar a skill atualiza o catálogo desde a última execução bem-sucedida e devolve o recibo curto — sem duplicar e sem reprocessar o que já está no catálogo.
|
||||
- Primeira execução (sem data anterior) faz a varredura inicial completa do que for encontrado; as seguintes são incrementais.
|
||||
- Achado duvidoso (ex.: falso positivo de "fala do Solla") fica marcado como "revisar" e não é ingerido até confirmação.
|
||||
- A skill reporta o que encontrou **e o que ficou de fora**; falha de um achado não derruba o lote (sucesso parcial com motivo no recibo).
|
||||
- **Guardrails:** a decisão sobre descoberta/download é a do C215 (instrução do dono sobre direitos, confirmada no gate) — a skill segue o mecanismo que o C215 definir, sem inventar um caminho paralelo; nenhuma segunda esteira de transcrição/classificação; estado da última execução é o único estado novo que este item cria.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não — o recibo (período, contagens, falhas, custo/tempo) é insumo operacional de quem roda a skill, não superfície de usuário.
|
||||
- **Decisões desbloqueadas:** N/A — a decisão é operacional: quando a comunicação precisa do acervo atualizado, quem opera roda a skill.
|
||||
- **Forma:** N/A.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- **Ideia do dono (verbatim, 2026-09-23):** "Crie uma skill que permita atualizar essa sessão fazendo um novo scraping a partir da data que foi feito o último [scraping]."
|
||||
- Nome da skill: `catalogo-falas-web`; wrapper de comando: `/catalogo-falas-web`.
|
||||
- Recorte temporal: **desde a data da última execução bem-sucedida**; sem data anterior, varredura inicial completa.
|
||||
- Recibo (campos literais): período coberto, achados, novos, ignorados, falhas (com motivo) e custo/tempo.
|
||||
- A análise automática (transcrição/classificação) é da ingestão (C215); a skill **pode** corrigir/curar o que a descoberta achou errado (ex.: falso positivo) antes de ingerir — nunca classifica faceta manualmente.
|
||||
- Operação por agente/coordenação técnica; **sem UI**.
|
||||
- Reexecutar não duplica nem reprocessa o que já está no catálogo.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `.agents/skills/catalogo-falas-web/SKILL.md` + wrapper fino `.opencode/commands/catalogo-falas-web.md`, no padrão das skills de conteúdo (`dossie-solla-{cidade,tema,instituicao}`, `briefing-capacitacao-solla`, `relatorio-cidade`, `reels-tutoriais`, `graficos-dados`); descoberta web por subagente com JSON datado, como o `research.json` dos dossiês.
|
||||
- **Precedente a olhar:** `dossie-solla-cidade` (research por subagente + pastas `data/` gitignored + recibo curto datado), `reels-tutoriais` (`sourceHash` em `reelPackageIngest.ts`), import da Câmara com janela explícita (`pnpm camara:import --date`, `scripts/import-camara-speeches.mjs`, `scripts/lib/camaraSpeeches.mjs`, skip por estado em `speechImport.ts`).
|
||||
- **Fatos verificados (a revalidar):** não há watermark de última execução no repo — o estado hoje é JSON por execução com `researchedAt`/`consultedAt`, recibos datados, saídas `<slug>-<YYYY-MM-DD>…` e changelog; a pesquisa web é feita por subagentes de skill (nunca por serviço do app — não há SERP no app nem yt-dlp no repo).
|
||||
- **Risco de acoplamento:** não criar segunda esteira de ASR/classificação nem segundo estado de catálogo; a ingestão é a do C215 e a descoberta não compete com a varredura oficial do Instagram/YouTube (C211/C212) — a decisão de mecanismo é a do C215.
|
||||
|
||||
## Dependências
|
||||
|
||||
- **C215 (dura)** — sem a esteira de ingestão a skill não tem o que orquestrar. **C216/C217 não bloqueiam.**
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- A esteira de download/transcrição/classificação (C215), a tela (C216) e os cortes (C217).
|
||||
- Paridade das gravações (C219); agendamento recorrente/infra de fila; monitoramento contínuo de redes.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **"Virar serviço de monitoramento 24/7."** Se alguém "só completar": agendador, fila, alerta em tempo real. **Corte neste item:** rodada manual sob demanda; recorrência é conversa separada.
|
||||
- **"Crawling irrestrito."** Se alguém "só completar": varrer a web inteira e perfis que não são do Solla. **Corte:** fontes/plataformas do escopo do C215, com queries pelo nome do deputado e variações.
|
||||
- **"Segunda esteira de ASR/classificação."** Se alguém "só completar": transcrever e classificar dentro da descoberta. **Corte:** a skill entrega o lote; análise é do C215.
|
||||
- **"Prometer cobertura exaustiva."** Se alguém "só completar": afirmar que nada escapou. **Corte:** o recibo declara período, achados e limites — o que ficou de fora é reportado, não escondido.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Onde mora o estado da última execução?** **Opções:** A) arquivo gitignored em `data/` (padrão das famílias de skill) | B) registro em banco | C) derivar do próprio catálogo (última data importada). **Recomendação:** A — segue o precedente das skills de conteúdo e não cria schema; o formato exato é decidido na implementação. _(confirmado no gate, 2026-09-24)_
|
||||
- **Recorte temporal do incremental?** **Opções:** A) desde a última execução bem-sucedida | B) janela fixa (ex.: últimos 30 dias) com dedupe. **Recomendação:** A — "a partir da data que foi feito o último", na voz do dono; janela fixa reintroduz reprocessamento. _(confirmado no gate, 2026-09-24)_
|
||||
- **Quem aciona?** **Opções:** A) manual (humano/agente rodando a skill, como os dossiês) | B) agendado. **Recomendação:** A — agendamento recorrente está fora de escopo; quando atualizar é decisão de quem opera. _(confirmado no gate, 2026-09-24)_
|
||||
- **O que fazer com achados duvidosos?** **Opções:** A) a skill marca "revisar" e não ingere até confirmação | B) ingere tudo e a curadoria corrige depois. **Recomendação:** A — evita lixo no catálogo e é barato neste volume; o duvidoso aparece no recibo. _(confirmado no gate, 2026-09-24)_
|
||||
- **Fontes de descoberta v1?** **Opções:** A) mesmas plataformas do C215 (YouTube, Instagram, áudio/rádio), com queries por nome do deputado e variações | B) recorte menor, monitorando veículo a veículo. **Recomendação:** A — manter o escopo do C215; ampliar é item próprio. _(confirmado no gate, 2026-09-24)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue: — (após `pnpm agent:register`)
|
||||
- Design UI (gate): N/A — sem UI
|
||||
- Abrir primeiro: `.agents/skills/dossie-solla-cidade/SKILL.md` (padrão de research por subagente + recibo) e `.agents/skills/reels-tutoriais/SKILL.md` (ingestão idempotente por hash)
|
||||
- `docs/plans/acervo-falas-web-ingestao.md` (C215 — mecanismo de descoberta/download e decisão sobre direitos)
|
||||
- `scripts/import-camara-speeches.mjs` / `scripts/lib/camaraSpeeches.mjs` / `src/utilities/speech/speechImport.ts` — janela explícita e skip por estado
|
||||
- `AGENTS.md` — convenções de skills do repo
|
||||
Loading…
Reference in a new issue