mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
docs(postmortem): capas do Instagram e clamp das legendas (PR #940)
Post-mortem do bug-fix com as duas causas-raiz (host fbcdn fora do allowlist + search:'' do pattern new URL), o pré-requisito do teste de calendário, a verificação local e as prevenções baratas implementadas e deferidas.
This commit is contained in:
parent
2dead731d8
commit
4afa0da53d
2 changed files with 97 additions and 0 deletions
1
docs/changelog/2026-09-11-instagram-midia-home.md
Normal file
1
docs/changelog/2026-09-11-instagram-midia-home.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**Capas do Instagram voltam a carregar na home e legendas longas não esticam mais o card (2026-09-11):** os cards do Instagram da seção "Acompanhe de perto" apareciam sem as capas e as legendas longas esticavam o card — a Graph API passou a servir a mídia de `*.fbcdn.net` (`instagram.<pop>.fna.fbcdn.net`), host fora do `images.remotePatterns` (que só cobria `*.cdninstagram.com` desde a entrega S3 de 2026-08-18, commit `a638a8ac`), e o otimizador respondia `400 "url" parameter is not allowed`; além disso, os patterns declarados como `new URL()` pinavam `search: ''` e o matcher do Next 15.4 exige igualdade estrita de `search`, rejeitando as URLs assinadas da Meta mesmo com o host certo. O fix (PR #940) troca os patterns do Instagram por objetos planos `{ protocol: 'https', hostname: '*.cdninstagram.com' | '**.fbcdn.net', pathname: '/**' }` e aplica `line-clamp-3` no título e `line-clamp-2` no subtítulo do `CampaignContentCard`; a regressão fica coberta por um guard unit que casa URLs assinadas reais contra o allowlist com o matcher do próprio Next (+ seam parser→allowlist) e por um e2e com legenda longa no stub do Instagram. Pré-requisito descoberto no gate: o `activityOverlay.unit.spec.tsx` era dependente da data (fixtures de agosto/2026) e quebrava a suíte unit full desde 01/09/2026 — sem o relógio fixo (commit `2dead731`) nenhum PR passaria, já que o wrapper de `changed` cai no full por um `--` extra repassado pelo pnpm. Verificação: unit full 2687 passed, int full 712 passed, e2e "Campaign home content section" 10/10 local, proxy real `200 image/jpeg` (antes 400), typecheck/lint/format/knip/cycles/build verdes no `pnpm push`; CI do PR a preencher e prod pendente (deploy manual + confirmação do humano).
|
||||
96
docs/postmortems/2026-09-11-instagram-midia-home.md
Normal file
96
docs/postmortems/2026-09-11-instagram-midia-home.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Post-mortem: capas do Instagram quebradas e legendas longas esticando o card na home de campanha
|
||||
|
||||
> Template do `/bug-fix`. Preencha com fatos apurados; o que não for apurado fica "não apurado" — nunca invente.
|
||||
|
||||
## Registro
|
||||
|
||||
| Campo | Valor |
|
||||
| ------------------- | --------------------------------------------------------- |
|
||||
| Data do post-mortem | 2026-09-11 |
|
||||
| Severidade | alta (home pública em reta eleitoral; sem perda de dados) |
|
||||
| Ambiente | prod (sintoma) + dev/worktree (diagnóstico e fix) |
|
||||
| Issue(s) | sem Issue (worktree `fix/2` do fluxo `/bug-fix`) |
|
||||
| PR do fix | #940 |
|
||||
| Detectado por | humano (relato do usuário) |
|
||||
|
||||
## Timeline
|
||||
|
||||
| Momento | Data/hora | Evento |
|
||||
| ------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Início provável | 2026-08-18 | commit `a638a8ac` (entrega S3 da seção de conteúdos do Instagram) criou o allowlist assumindo `*.cdninstagram.com` — `docs/plans/secao-conteudos-home-instagram-impl.md:110`; a data em que a Meta migrou a mídia para `*.fbcdn.net` não foi apurada |
|
||||
| Detecção | 2026-09-11 | relato do usuário: capas do Instagram sem carregar e legendas longas esticando o card na home pública |
|
||||
| Correção mergeada | 2026-09-11 | commit `a83b6dd5` (fix) e commit `2dead731` (pré-requisito do gate) no PR #940 — merge não apurado no momento da escrita |
|
||||
| Deploy | pendente | deploy é manual (`deploy.yml`, `workflow_dispatch`); não disparado até a escrita |
|
||||
| Verificado em prod | pendente | depende do deploy manual + confirmação do humano |
|
||||
|
||||
## O bug
|
||||
|
||||
Na seção "Acompanhe de perto" da home pública, os cards do Instagram apareciam, mas as capas/mídias não carregavam — o card renderizava com a moldura e os metadados, sem a imagem. Em paralelo, legendas longas do Instagram esticavam demais o card, sem limite visual nem ellipsis, quebrando o bento. **Sintoma — não a causa.**
|
||||
|
||||
## Causa-raiz
|
||||
|
||||
### Mídia (duas camadas)
|
||||
|
||||
1. **Host fora do allowlist:** a Instagram Graph API passou a servir a mídia de `*.fbcdn.net` (ex.: `https://instagram.fssa2-1.fna.fbcdn.net/v/...jpg?stp=...&_nc_ohc=...&oe=...`), mas `next.config.mjs` só permitia `https://*.cdninstagram.com/**`; o otimizador do Next respondia `400 "url" parameter is not allowed` para toda capa.
|
||||
2. **Pattern com `search` pinado:** os patterns eram declarados como `new URL(...)`, que pina `search: ''`; o matcher do Next 15.4 (`next/dist/shared/lib/match-remote-pattern`, picomatch) exige igualdade estrita de `search`, então mesmo com o host certo a URL real (assinada, com query string) seria rejeitada.
|
||||
|
||||
5-whys:
|
||||
|
||||
1. **Por que a capa não carrega?** O otimizador responde 400 `"url" parameter is not allowed` para a URL de mídia.
|
||||
2. **Por que o otimizador rejeita?** O host da URL (`*.fbcdn.net`) não está em `images.remotePatterns`.
|
||||
3. **Por que o allowlist não cobre o host?** A suposição do host nasceu na entrega S3 (`docs/plans/secao-conteudos-home-instagram-impl.md:110`, commit `a638a8ac`, 2026-08-18), quando a Meta ainda servia de `cdninstagram`; a migração da Meta para `fbcdn` não foi detectada.
|
||||
4. **Por que corrigir só o host não bastaria?** Porque `new URL()` pina `search: ''` e o matcher do Next 15.4 exige igualdade estrita — as URLs de mídia são assinadas (têm query string), então continuariam rejeitadas.
|
||||
5. **Por que o loop local não pegou?** O stub e2e do Instagram serve as miniaturas de `localhost`; nenhum teste casava as URLs reais (host + assinatura) contra o allowlist de produção.
|
||||
|
||||
**Evidência:** em prod, `GET /_next/image?url=<fbcdn-url-assinada>&w=384&q=75` → `400` com body `"url" parameter is not allowed` (read-only via curl); a URL direta no CDN → `200 image/jpeg`. No dev local, o mesmo proxy → 400 antes e `200 image/jpeg 19695 bytes` depois do fix. O unit novo falhava sem o fix (2 hosts) e passa com.
|
||||
|
||||
### Texto
|
||||
|
||||
O `<h3>` do card (`src/components/CampaignContentCard.tsx`) não tinha clamp, e a legenda inteira do Instagram vira título (`toInstagramCardData` em `src/components/CampaignContentSection.tsx:77`). O card foi desenhado para títulos de artigo; em prod, o card em destaque tinha legenda de 370 caracteres.
|
||||
|
||||
5-whys:
|
||||
|
||||
1. **Por que o card estica?** O `<h3>` cresce com o texto, sem limite de linhas.
|
||||
2. **Por que o texto é longo?** A legenda do Instagram vira título do card — a plataforma não tem título curto.
|
||||
3. **Por que ninguém viu antes?** O fixture e2e do Instagram usava legenda curta; nenhum teste exercitava legenda longa.
|
||||
|
||||
**Evidência:** o e2e do clamp falhava com computed `-webkit-line-clamp: none` e passa com `3`.
|
||||
|
||||
## Correção
|
||||
|
||||
- `next.config.mjs`: os patterns do Instagram viram objetos planos `{ protocol: 'https', hostname: '*.cdninstagram.com' | '**.fbcdn.net', pathname: '/**' }` (sem `search`), cobrindo o host novo e as URLs assinadas.
|
||||
- `src/components/CampaignContentCard.tsx`: `line-clamp-3` no título e `line-clamp-2` no subtítulo.
|
||||
- Testes: novo `tests/unit/nextImageRemotePatterns.unit.spec.ts` (casa URLs assinadas reais contra `nextConfig.images.remotePatterns` com o matcher do próprio Next + seam parser→allowlist); stub e2e com legenda longa no reel (`tests/e2e/instagram-stub.mjs`) e asserção de clamp em `tests/e2e/frontend.e2e.spec.ts`.
|
||||
- Pré-requisito descoberto no gate: `tests/unit/activityOverlay.unit.spec.tsx` era dependente da data (calendário abre no mês corrente; fixtures de agosto/2026) e quebrava a suíte unit full desde 01/09/2026 — sem o relógio fixo no mês do fixture (commit `2dead731`) nenhum PR passaria no gate. O wrapper `scripts/vitest-changed-or-full.mjs` roda full na prática por um `--` extra que o pnpm repassa ao vitest (o modo `changed` cai no full), o que expôs a falha.
|
||||
|
||||
Resolve as causas: o allowlist volta a aceitar os hosts reais com URL assinada e o card limita o texto independentemente do tamanho da legenda. Sem migration, sem mudança de access.
|
||||
|
||||
## Verificação
|
||||
|
||||
- Teste de regressão: `tests/unit/nextImageRemotePatterns.unit.spec.ts` — falha sem o fix (2 hosts) e passa com; e2e do clamp em `tests/e2e/frontend.e2e.spec.ts` — falha com `-webkit-line-clamp: none` e passa com `3`
|
||||
- Suíte: unit full 2687 passed; int full 712 passed; e2e frontend do describe "Campaign home content section" 10/10 local; proxy real `200 image/jpeg`; typecheck/lint/format/knip/cycles/build verdes no `pnpm push` (gate:ci)
|
||||
- CI: a preencher — não apurado no momento da escrita (PR #940)
|
||||
- Prod: pendente (deploy manual + confirmação do humano)
|
||||
|
||||
## Prevenção
|
||||
|
||||
| Estratégia | Custo | Estado |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| Guard unit do allowlist com URLs assinadas reais + seam parser→allowlist (`tests/unit/nextImageRemotePatterns.unit.spec.ts`) | barata | implementada agora (PR #940) |
|
||||
| e2e de line-clamp com legenda longa (`tests/e2e/instagram-stub.mjs` + asserção em `tests/e2e/frontend.e2e.spec.ts`) | barata | implementada agora (PR #940) |
|
||||
| Relógio fixo do teste de calendário (`tests/unit/activityOverlay.unit.spec.tsx`, commit `2dead731`) | barata | implementada agora (pré-requisito do gate) |
|
||||
| Mapear `CampaignContentCard/Section` + `src/utilities/socialFeed/**` → spec `frontend` no `scripts/lib/e2e-affected-manifest.mjs` | barata | deferida — não implementada neste PR (o arquivo é high-risk e força unit full; precisa vir depois do fix do calendário) |
|
||||
| Corrigir o `--` extra do `scripts/vitest-changed-or-full.mjs` (o pnpm repassa o `--` ao vitest e o modo `changed` cai no full) | barata | deferida — não implementada neste PR |
|
||||
| Re-host da capa do Instagram no sync (baixar e servir via S3/Garage + `/api/media/file/...`) para não depender de URL assinada que expira (`oe`) | cara | documentada — não implementada neste fluxo |
|
||||
| Observabilidade do allowlist/idade do snapshot no painel de sync | cara | documentada — não implementada neste fluxo |
|
||||
|
||||
**Estratégia implementada:** guard unit do allowlist contra as URLs assinadas reais com o matcher do próprio Next (fecha a lacuna que o stub local não via); e2e de line-clamp com legenda longa; relógio fixo do teste de calendário para o gate voltar a passar.
|
||||
|
||||
**Estratégia documentada (cara):** re-host da capa do Instagram no sync (baixar e servir via S3/Garage + `/api/media/file/...`), eliminando a dependência de URL assinada que expira no snapshot (`oe`); observabilidade do allowlist/idade do snapshot no painel de sync. Gatilhos para promover: primeiro card 403 do proxy/CDN ou sync falho além do `oe`; nova migração de CDN da Meta.
|
||||
|
||||
## Lições
|
||||
|
||||
- **Allowlist de CDN externo envelhece sem guard:** a suposição do host nasceu no S3 e ficou correta até a Meta migrar a mídia para `fbcdn`. Um stub e2e que serve de `localhost` nunca cobre o host real; o guard útil é casar as URLs reais (host + assinatura) contra o allowlist com o matcher da própria ferramenta.
|
||||
- **`new URL()` como pattern é uma armadilha silenciosa:** a forma "elegante" pina `search: ''` e muda a semântica do allowlist — só apareceu porque as URLs são assinadas. O contrato precisa de teste, não de leitura de código.
|
||||
- **Teste dependente de data bloqueia o gate inteiro:** o `activityOverlay` quebrava a suíte unit full desde 01/09/2026 e o wrapper de `changed` caía no full por um `--` extra do pnpm — sem o fix do relógio, nenhum PR passaria. Dívida de teste de calendário é dívida de fluxo.
|
||||
- **Fixture curta esconde bug de texto longo:** o e2e do Instagram usava legenda curta; a produção tinha 370 caracteres no card em destaque. Fixtures de UI de texto devem incluir o caso extremo que a fonte real entrega.
|
||||
Loading…
Reference in a new issue