plan(S11): feed do Instagram configurado não aparece na home pública — intenção + rascunho UI do painel de status

This commit is contained in:
Francisco Solla 2026-08-19 20:45:45 -03:00 committed by github-actions[bot]
parent 21a8e39fe2
commit 851ac3941b
5 changed files with 362 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View file

@ -0,0 +1,269 @@
<!doctype html>
<html lang="pt-BR">
<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>UI draft — Feed do Instagram configurado não aparece na home</title>
</head>
<body class="bg-zinc-950 text-zinc-100 antialiased">
<main class="mx-auto max-w-6xl p-8">
<p class="text-sm text-zinc-400">
Assessoria de comunicação configura o feed do Instagram no admin (global "Feed de redes
sociais") e precisa ver, no mesmo lugar, por que os posts não aparecem na home pública.
</p>
<!-- Cena: falha na sincronização (o caso real em produção) -->
<section data-shot="admin-falha-token" class="mt-10">
<p class="mb-3 text-sm font-medium text-zinc-300">Cena 1 — token recusado pela Graph API</p>
<div class="rounded-xl border border-zinc-700 bg-zinc-100 text-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-300 px-6 py-4">
<div class="flex items-center gap-2">
<span class="rounded bg-zinc-900 px-2 py-0.5 text-xs font-semibold text-zinc-100">
Configurações
</span>
<span class="text-sm text-zinc-600">/ Feed de redes sociais</span>
</div>
<span class="rounded border border-zinc-400 bg-white px-3 py-1 text-xs text-zinc-600">
Draft · Salvar · Cancelar
</span>
</div>
<div class="px-6 py-5">
<h1 class="text-lg font-bold">Feed de redes sociais</h1>
<p class="mt-1 max-w-2xl text-sm text-zinc-600">
Fonte automática do board "Acompanhe de perto" da home de campanha. Sem chave
configurada, os cards da plataforma não aparecem.
</p>
<div class="mt-5 rounded-lg border border-red-300 bg-red-50 p-4">
<p class="text-sm font-bold text-red-800">Falha na última sincronização</p>
<p class="mt-1 max-w-2xl text-sm text-red-700">
A Graph API recusou o token configurado (token inválido/expirado ou emitido via
Facebook Login — a Graph API só aceita tokens gerados pelo Instagram Login).
Enquanto isso, nenhum post do Instagram aparece na home.
</p>
<p class="mt-1 max-w-2xl text-sm text-red-700">
<strong>Correção:</strong> gere um token de longa duração pelo Instagram Login
(conta Business/Creator vinculada à página) e atualize o campo abaixo.
</p>
<button class="mt-3 rounded-md bg-red-600 px-3 py-1.5 text-sm font-semibold text-white">
Tentar sincronizar de novo
</button>
</div>
<div class="mt-6 grid max-w-2xl grid-cols-1 gap-4">
<div>
<label class="text-xs font-semibold text-zinc-700">Token de acesso (Instagram Graph API)</label>
<div class="mt-1 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-700">
IGAA...abc123
</div>
</div>
<div>
<label class="text-xs font-semibold text-zinc-700">ID do usuário (conta Business/Creator)</label>
<div class="mt-1 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-700">
17841400000000000
</div>
</div>
<div>
<label class="text-xs font-semibold text-zinc-700">Máximo de posts</label>
<div class="mt-1 w-24 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-700">
3
</div>
</div>
</div>
<div class="mt-6 max-w-2xl rounded-lg border border-zinc-300 bg-white p-4">
<p class="text-sm font-bold text-zinc-800">Instagram — posts recentes</p>
<p class="mt-1 text-sm text-zinc-500">
Nenhum post sincronizado. Após uma sincronização bem-sucedida, os últimos posts
aparecem aqui para marcar os que não devem ir para a home.
</p>
</div>
<div class="mt-6 flex items-center justify-end gap-2 border-t border-zinc-300 pt-4">
<button class="rounded-md border border-zinc-400 bg-white px-4 py-1.5 text-sm font-semibold text-zinc-700">
Cancelar
</button>
<button class="rounded-md bg-red-600 px-4 py-1.5 text-sm font-semibold text-white">
Salvar
</button>
</div>
</div>
</div>
</section>
<!-- Cena: sincronizado, picker com posts -->
<section data-shot="admin-ok" class="mt-12">
<p class="mb-3 text-sm font-medium text-zinc-300">Cena 2 — sincronizado com sucesso</p>
<div class="rounded-xl border border-zinc-700 bg-zinc-100 text-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-300 px-6 py-4">
<div class="flex items-center gap-2">
<span class="rounded bg-zinc-900 px-2 py-0.5 text-xs font-semibold text-zinc-100">
Configurações
</span>
<span class="text-sm text-zinc-600">/ Feed de redes sociais</span>
</div>
<span class="rounded border border-zinc-400 bg-white px-3 py-1 text-xs text-zinc-600">
Draft · Salvar · Cancelar
</span>
</div>
<div class="px-6 py-5">
<h1 class="text-lg font-bold">Feed de redes sociais</h1>
<div class="mt-5 rounded-lg border border-zinc-300 bg-white p-4">
<p class="text-sm font-bold text-zinc-800">Sincronizado · há 2 min · 4 posts</p>
<p class="mt-1 max-w-2xl text-sm text-zinc-500">
Próxima atualização automática em ~5 min. Os 4 posts mais recentes estão no board
"Acompanhe de perto" da home.
</p>
</div>
<div class="mt-6 grid max-w-2xl grid-cols-1 gap-4">
<div>
<label class="text-xs font-semibold text-zinc-700">Token de acesso (Instagram Graph API)</label>
<div class="mt-1 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-700">
IGAA...abc123
</div>
</div>
<div>
<label class="text-xs font-semibold text-zinc-700">ID do usuário (conta Business/Creator)</label>
<div class="mt-1 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-700">
17841400000000000
</div>
</div>
<div>
<label class="text-xs font-semibold text-zinc-700">Máximo de posts</label>
<div class="mt-1 w-24 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-700">
3
</div>
</div>
</div>
<div class="mt-6 max-w-2xl rounded-lg border border-zinc-300 bg-white p-4">
<p class="text-sm font-bold text-zinc-800">Instagram — posts recentes</p>
<p class="mt-1 text-sm text-zinc-500">
Marque "Não exibir" para tirar um post do board (ex.: posts de grade). O board pula
para o próximo elegível.
</p>
<ul class="mt-3 divide-y divide-zinc-200">
<li class="flex items-center gap-3 py-2.5">
<span class="h-10 w-10 shrink-0 rounded bg-zinc-300"></span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-zinc-800">
Caravana pelo interior: chegada em Vitória da Conquista
</span>
<span class="block text-xs text-zinc-500">há 2 h · ID 17841400000000000_001</span>
</span>
<span class="rounded bg-zinc-200 px-2 py-1 text-xs font-semibold text-zinc-700">
Exibir
</span>
</li>
<li class="flex items-center gap-3 py-2.5">
<span class="h-10 w-10 shrink-0 rounded bg-zinc-300"></span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-zinc-800">
Foto do plenário: votação da pauta da saúde
</span>
<span class="block text-xs text-zinc-500">ontem · ID 17841400000000000_002</span>
</span>
<span class="rounded bg-zinc-200 px-2 py-1 text-xs font-semibold text-zinc-700">
Exibir
</span>
</li>
<li class="flex items-center gap-3 py-2.5">
<span class="h-10 w-10 shrink-0 rounded bg-zinc-300"></span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-zinc-800">
Post de grade (sem legenda) — para marcar "Não exibir"
</span>
<span class="block text-xs text-zinc-500">ontem · ID 17841400000000000_003</span>
</span>
<span class="rounded bg-red-100 px-2 py-1 text-xs font-semibold text-red-700">
Não exibir
</span>
</li>
<li class="flex items-center gap-3 py-2.5">
<span class="h-10 w-10 shrink-0 rounded bg-zinc-300"></span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-zinc-800">
Agenda da semana: agendas abertas em Salvador
</span>
<span class="block text-xs text-zinc-500">há 2 dias · ID 17841400000000000_004</span>
</span>
<span class="rounded bg-zinc-200 px-2 py-1 text-xs font-semibold text-zinc-700">
Exibir
</span>
</li>
</ul>
</div>
<div class="mt-6 flex items-center justify-end gap-2 border-t border-zinc-300 pt-4">
<button class="rounded-md border border-zinc-400 bg-white px-4 py-1.5 text-sm font-semibold text-zinc-700">
Cancelar
</button>
<button class="rounded-md bg-red-600 px-4 py-1.5 text-sm font-semibold text-white">
Salvar
</button>
</div>
</div>
</div>
</section>
<!-- Cena: ainda não configurado (primeiro acesso) -->
<section data-shot="admin-nao-configurado" class="mt-12">
<p class="mb-3 text-sm font-medium text-zinc-300">Cena 3 — sem token nem ID (primeiro acesso)</p>
<div class="rounded-xl border border-zinc-700 bg-zinc-100 text-zinc-900">
<div class="flex items-center justify-between border-b border-zinc-300 px-6 py-4">
<div class="flex items-center gap-2">
<span class="rounded bg-zinc-900 px-2 py-0.5 text-xs font-semibold text-zinc-100">
Configurações
</span>
<span class="text-sm text-zinc-600">/ Feed de redes sociais</span>
</div>
<span class="rounded border border-zinc-400 bg-white px-3 py-1 text-xs text-zinc-600">
Draft · Salvar · Cancelar
</span>
</div>
<div class="px-6 py-5">
<h1 class="text-lg font-bold">Feed de redes sociais</h1>
<div class="mt-5 max-w-2xl rounded-lg border border-zinc-300 bg-white p-4">
<p class="text-sm font-bold text-zinc-800">Instagram ainda não configurado</p>
<p class="mt-1 text-sm text-zinc-500">
Informe o token de acesso e o ID da conta para publicar posts do Instagram na home.
Sem configuração, o board funciona com artigos e YouTube.
</p>
</div>
<div class="mt-6 grid max-w-2xl grid-cols-1 gap-4">
<div>
<label class="text-xs font-semibold text-zinc-700">Token de acesso (Instagram Graph API)</label>
<div class="mt-1 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-400">
(vazio)
</div>
</div>
<div>
<label class="text-xs font-semibold text-zinc-700">ID do usuário (conta Business/Creator)</label>
<div class="mt-1 rounded-md border border-zinc-400 bg-white px-3 py-2 text-sm text-zinc-400">
(vazio)
</div>
</div>
</div>
<div class="mt-6 flex items-center justify-end gap-2 border-t border-zinc-300 pt-4">
<button class="rounded-md border border-zinc-400 bg-white px-4 py-1.5 text-sm font-semibold text-zinc-700">
Cancelar
</button>
<button class="rounded-md bg-red-600 px-4 py-1.5 text-sm font-semibold text-white">
Salvar
</button>
</div>
</div>
</div>
</section>
</main>
</body>
</html>

View file

@ -0,0 +1,93 @@
# Feed do Instagram configurado não aparece na home pública
Status: aprovado
Atualizado em: 2026-08-19
Issue: #115
Priority: P1
Model: cursor-grok-4.5-medium
Impeccable: C — encaixe na global do admin (painel de status de sincronização)
Rascunho UI: docs/plans/feed-instagram-nao-aparece-home-ui-draft.html + PNGs embutidos abaixo
Appetite: ~1 dia eng + diagnóstico da causa em produção
Responsável: —
## Intenção
A assessoria preencheu o token e o ID do Instagram na configuração do feed (global "Feed de redes sociais", entregue na S3) e os posts **não aparecem** na seção "Acompanhe de perto" da home pública — em produção, o board mostra YouTube e artigos, nenhum card do Instagram e nenhum link "Seguir no Instagram →" (verificado em 2026-08-19 em jorgesolla1313.com.br). O desenho da S3 é fail-closed **silencioso**: qualquer falha da API (token recusado, ID inválido, rede) degrada para "sem cards" sem deixar rastro — o único indício hoje é o picker do admin vazio ("Nenhum post recente"), que não diz **por quê**. A assessoria fica num ciclo cego de tentativa-e-erro de credenciais.
## Persona e fluxo
- **Persona / contexto:** assessoria de comunicação, configurando a global no admin do Payload (grupo "Configurações"), sem acesso ao código nem a logs.
- **Job principal:** saber, no mesmo lugar onde configurou o feed, se a sincronização do Instagram funcionou — e, se não, **por quê** e **o que fazer**.
- **Fluxo desejado:**
1. A assessoria preenche token + ID e salva a global.
2. O admin mostra o estado da sincronização: "Sincronizado · há X min · N posts" (e os posts aparecem no picker, prontos para exclusão), ou "Falha na última sincronização" com o motivo e a correção esperada (ex.: token inválido/expirado ou emitido via Facebook Login — gerar pelo Instagram Login).
3. Com sincronização OK, os posts aparecem na home pública em minutos (cache revalidado no salvar).
4. A causa da ocorrência atual fica diagnosticada e documentada num runbook curto para a assessoria não repetir o ciclo.
- **Anti-goals de produto:** nada de wizard OAuth "conectar com Facebook" dentro do admin; nada de dashboard de saúde de feeds; nada de embeds/scripts do Instagram no site público; nada de métricas de engajamento.
### Esboço de fluxo (B/C/D)
```text
[admin: global Feed de redes sociais]
→ salvar token+ID → [status: Sincronizado · N posts] → [home pública: cards IG em minutos]
→ [status: Falha + motivo + correção] → [assessoria corrige credencial e salva] → [status OK]
```
### Rascunho UI (B/C/D)
Painel de status de sincronização por plataforma na global do admin, embutido junto aos campos do Instagram (e reaproveitável para YouTube depois, se a paridade for pedida).
![Rascunho UI — admin, falha de token](feed-instagram-nao-aparece-home-ui-draft-admin-falha-token.png)
![Rascunho UI — admin, sincronizado com picker](feed-instagram-nao-aparece-home-ui-draft-admin-ok.png)
![Rascunho UI — admin, não configurado](feed-instagram-nao-aparece-home-ui-draft-admin-nao-configurado.png)
## Objetivo e aceite
- Com credenciais válidas (token gerado pelo Instagram Login + ID da conta Business/Creator), os posts do Instagram aparecem na seção "Acompanhe de perto" da home em até alguns minutos após salvar a configuração — badge "Instagram", legenda, data relativa, clique abre na plataforma. Sem ação manual além da config.
- Quando a Graph API recusa as credenciais ou a sincronização falha, o admin mostra o motivo em linguagem de produto e a correção esperada (ex.: "token inválido/expirado ou emitido via Facebook Login — gere pelo Instagram Login"). A assessoria deixa de ficar cega.
- A home pública continua fail-closed: API fora ou credenciais inválidas nunca quebram a página nem vazam erro — o board segue com artigos + YouTube (comportamento atual preservado).
- A causa da ocorrência em produção fica diagnosticada e registrada em runbook curto (passos para gerar/validar o token), para o ciclo de tentativa-e-erro não se repetir.
- Guardrails: token nunca exposto fora do admin (read da global admin-only, como hoje); zero embeds/scripts do Instagram no site público (LCP); identificadores em inglês, copy em pt.
## Dados (intenção)
- **Vou apresentar dados?** Não — o painel mostra estado operacional (última sincronização, nº de posts, motivo de erro), não métricas. Nenhuma decisão de análise desbloqueada.
- **Forma:** adiada ao plano de implementação — sem contadores, sem engajamento.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/utilities/socialFeed/instagramFeed.ts` (onde o erro é engolido no fail-closed — fonte do "porquê"), `src/globals/SocialFeedSettings.ts` (superfície do status), `src/components/admin/InstagramPostExclusionPicker.tsx` (pickers/superfície admin já existente, mesma família de componente), `src/utilities/revalidateRequest.ts` (tag `social-feed` — o salvar já revalida; não espera-se tocar).
- **Precedente a olhar:** plano/impl da S3 (`docs/plans/secao-conteudos-home-instagram*.md`), irmão `youtubeFeed.ts`, e2e do describe serial da seção (stub IG) e smoke do picker no admin.
- **Risco de acoplamento:** o fetch roda dentro de `unstable_cache` durante render — qualquer persistência de status precisa respeitar a restrição de não chamar `revalidateTag` dentro de cache (precedente documentado da S2/S3). A home pública é ISR; revalidação via tag já cobre o salvar do admin.
## Dependências
- **S3** (pai, `done`/`in-prod` — imutável; este é item **sucessor**).
## Fora de escopo
- Setup OAuth / "conectar com Facebook" dentro do admin (o fluxo Meta fica com a assessoria; runbook documenta).
- Redesenho do board público ou dos cards (superfície aprovada na S3 não muda).
- Paridade de status para o YouTube (pode virar followup se a plataforma pedir; o desenho do painel é por plataforma).
- Backfill retroativo de snapshot.
## Rabbit holes de produto
- **Dashboard de saúde de feeds.** Se alguém "só completar" a ideia de status, vira painel de monitoramento com histórico e alertas. **Corte neste item:** um painel de status por plataforma na própria global, só estado atual + motivo; sem histórico, sem alertas.
- **Wizard OAuth no admin.** Completar o desejo com o fluxo completo da Meta explode o escopo (app Meta, permissões, redirects). **Corte neste item:** credenciais continuam preenchidas manualmente; o valor é diagnóstico e correção, não aquisição de token.
- **Investigação infinita da causa em produção.** A causa pode ser credencial da assessoria (troca fora do nosso controle). **Corte neste item:** diagnóstico com prazo — o que não for descoberto num período curto vira instrução de validação no runbook, não bloqueio do item.
## Questões em aberto (produto)
- **Onde a assessoria vê o "porquê"?** **Opções:** A) painel de status na própria global do feed (ao lado dos campos do Instagram) — o lugar onde a assessoria já está quando configura | B) só logs/exceção no servidor (exige acesso técnico — não resolve para a persona) | C) nada além do picker vazio (status quo). **Recomendação:** A — o fracasso acontece onde a configuração acontece; o sinal pertence a esse mesmo lugar. _(assumido — validar com produto)_
- **O item cobre a ocorrência atual em produção?** **Opções:** A) diagnóstico da causa atual + painel de status (a correção de credencial fica com a assessoria, documentada) | B) só painel de status; a causa atual fica para followup | C) só runbook de credenciais. **Recomendação:** A — sem o diagnóstico, não sabemos se há também um defeito de código por trás do sintoma; sem o painel, o sintoma se repete cegamente. _(assumido — validar com produto)_
- **Paridade de status para YouTube agora?** **Opções:** A) só Instagram nesta fatia (o YouTube funciona em produção; o painel nasce desenhado por plataforma) | B) YouTube e Instagram juntos. **Recomendação:** A — fatia mínima útil; paridade vira followup barato se pedida. _(assumido — validar com produto)_
## Referências
- Issue pai S3 (#20, `done`/`in-prod`) + `docs/plans/secao-conteudos-home-instagram.md` e `-impl.md`
- Rascunho UI (gate): `docs/plans/feed-instagram-nao-aparece-home-ui-draft.html` + PNGs acima
- Primeiras pistas para o executor: `src/utilities/socialFeed/instagramFeed.ts` (caminho de falha silenciosa), `src/globals/SocialFeedSettings.ts`, `src/components/admin/InstagramPostExclusionPicker.tsx`, `tests/e2e/frontend.e2e.spec.ts` (describe serial da seção)
- `docs/AGENT-OPS.md` (serializes: nenhum recurso compartilhado — `serializes: []`)