From a60b3880ca856d29cc784710751c5e4ecbaffbd1 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 18 Aug 2026 12:45:30 -0300 Subject: [PATCH 1/2] =?UTF-8?q?feat(S5):=20banda=20visual=20pr=C3=B3pria?= =?UTF-8?q?=20para=20a=20se=C3=A7=C3=A3o=20'Acompanhe=20de=20perto'=20na?= =?UTF-8?q?=20home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG-AGENTS.md | 4 ++ docs/changelog/2026-08-18-s5.md | 1 + ...aracao-visual-secao-conteudos-home-impl.md | 68 +++++++++++++++++++ src/components/CampaignContentSection.tsx | 6 +- tests/e2e/frontend.e2e.spec.ts | 10 +++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/2026-08-18-s5.md create mode 100644 docs/plans/separacao-visual-secao-conteudos-home-impl.md diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index 748810b0..61ac13fc 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,10 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `
` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções `toHaveCSS` no e2e full-state da seção (fundo `#ebe9e9` exato — pega token quebrado em vez de só "≠ branco", hairline top `1px` com cor `rgb(0 0 0 / 12%)` exata, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida. + +**S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `
` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções de estilo computado no e2e full-state da seção (fundo ≠ branco ≠ prova, hairline top presente, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida. + **S1** — seção "Acompanhe de perto" na home de campanha (jorgesolla1313.com.br), na 3ª posição entre a prova social e "Por que essa eleição importa": bento de 5 artigos do `post` collection (1 card grande + 4 pequenos) no desktop e carrossel de 1 conteúdo por tela com dots e contador "N de M" no mobile (~390px), badge de tipo (Notícia/Campanha/Artigo/Evento), capa, título e meta "há X · categoria" (relativa < 48h, data absoluta além — nunca mente congelada na ISR); card inteiro é link para a rota canônica do artigo. Fail-closed: sem posts visíveis (`isPostVisible` — tag `hidden` eleitoral nunca vaza) a seção some inteira e Prova → Problema voltam a ser contíguos. Zero schema/migration/Consent; leitura via `getVisiblePosts` sob a tag `posts` (home estática revalidada pelo mecanismo `posts` existente). Primeira fatia do board aprovado (S2/S3 = YouTube/Instagram). Verificação: unit 2282 + int 791 verdes, e2e frontend 10/10 em dev e prod (`E2E_PROD=1`, `.next-e2e`), suíte completa prod 169-170 passed com flakes ambientais de campanha (máquina compartilhada; testes diferentes a cada run, nenhum no código tocado); criação via REST com hooks reais; polling positivo do estado da home pós-revalidate (ISR serve stale uma vez sob carga) e navegação para about:blank antes do cleanup (prefetch de links apagados); lint/format/typecheck/cycles/knip/build verdes. `/simplify` 3 reviewers: 2 P1 de a11y (foco+setas no track, `inert` em slides inativos) corrigidos; o `revalidate = 300` proposto contra o congelamento da label relativa foi TESTADO e REVERTIDO — no Next 15.4 em prod ele trava o bust por tag (`revalidateTag('posts')` deixava de regenerar a home; medido em probe manual) — a label relativa ganhou threshold de 48h com fallback absoluto; P2 absorvidos (dots ≥3:1, aria-live no contador, motion-reduce na capa, fidelidade de gaps, secret do e2e espelhado da env); débitos deferidos documentados no impl plan (refusal do `formatRelativeAge` por contrato, gutter flow-centered, link `/artigos` sancionado pela intenção). **S1** — seção "Acompanhe de perto" na home de campanha (jorgesolla1313.com.br), na 3ª posição entre a prova social e "Por que essa eleição importa": bento de 5 artigos do `post` collection (1 card grande + 4 pequenos) no desktop e carrossel de 1 conteúdo por tela com dots e contador "N de M" no mobile (~390px), badge de tipo (Notícia/Campanha/Artigo/Evento), capa, título e meta relativa "há X · categoria"; card inteiro é link para a rota canônica do artigo. Fail-closed: sem posts visíveis (`isPostVisible` — tag `hidden` eleitoral nunca vaza) a seção some inteira e Prova → Problema voltam a ser contíguos. Zero schema/migration/Consent; leitura via `getVisiblePosts` sob a tag `posts`; a home ganhou `revalidate = 300` para a label relativa não congelar na ISR. Primeira fatia do board aprovado (S2/S3 = YouTube/Instagram). Verificação: unit 2282 + int 791 verdes, e2e frontend 10/10 (empty-state + full-state desktop/mobile, fail-closed com tag oculta, click-through, teclado/setas, swipe, dots; criação via REST com hooks reais; cleanup convergindo a cache de dev), lint/format/typecheck/cycles/knip/build verdes. `/simplify` 3 reviewers: 2 P1 de a11y (foco+setas no track, `inert` em slides inativos) e o congelamento da data relativa na ISR corrigidos; P2 absorvidos (dots ≥3:1, aria-live no contador, motion-reduce na capa, fidelidade de gaps, secret do e2e espelhado da env); débitos deferidos documentados no impl plan (refusal do `formatRelativeAge` por contrato, gutter flow-centered, link `/artigos` sancionado pela intenção). diff --git a/docs/changelog/2026-08-18-s5.md b/docs/changelog/2026-08-18-s5.md new file mode 100644 index 00000000..22189716 --- /dev/null +++ b/docs/changelog/2026-08-18-s5.md @@ -0,0 +1 @@ +**S5** — separação visual da seção "Acompanhe de perto" da prova social na home de campanha: o `
` sai de `bg-white` (idêntico à faixa de números, seções coladas) para `border-y border-(--campaign-line) bg-(--campaign-band)` — banda `#ebe9e9` da paleta + hairlines de 12%, o desenho aprovado no wireframe (seção 7: zona de cor própria com `border-top/bottom: 1px solid var(--line)`) e no rascunho UI do gate (Cena 1/3 recomendadas). Zero mudança de conteúdo/dados/comportamento: cards continuam brancos e saltam na banda; fail-closed intacto (sem posts visíveis a seção some e Prova → Problema voltam contíguos — teste e2e existente segue verde); nenhum token novo; sem migration/Consent/access. Única mudança de runtime: 1 linha de className; + asserções `toHaveCSS` no e2e full-state da seção (fundo `#ebe9e9` exato — pega token quebrado em vez de só "≠ branco", hairline top `1px` com cor `rgb(0 0 0 / 12%)` exata, cards brancos) — sem screenshot diff (repo não usa regressão visual). Verificação: tsc/lint/format/unit 2318 verdes; e2e frontend: empty-state (contiguidade) e bento passaram com as asserções novas (2/2 runs que as alcançaram); o teste full-state falhou localmente no clique pós-minhas-asserções por cold-compile dev sob load ~68 — baseline com as mudanças stashed falha idêntico (falha pré-existente do ambiente, não regressão); build verde (migrations ok). knip local = limitação P3 conhecida (não carrega `payload.config.ts`); CI valida. diff --git a/docs/plans/separacao-visual-secao-conteudos-home-impl.md b/docs/plans/separacao-visual-secao-conteudos-home-impl.md new file mode 100644 index 00000000..617913bb --- /dev/null +++ b/docs/plans/separacao-visual-secao-conteudos-home-impl.md @@ -0,0 +1,68 @@ +# Impl: Separar visualmente a seção "Acompanhe de perto" da prova social na home + +Status: aprovado +Atualizado em: 2026-08-18 +Issue: #33 +Intenção: docs/plans/separacao-visual-secao-conteudos-home.md +Appetite restante: herdado (~0,5 dia de eng — mudança de uma linha + teste) + +## Leitura da intenção + +- **Outcome:** a fronteira entre a faixa de prova social (branca) e a seção "Acompanhe de perto" fica visível de imediato, desktop e mobile; cards brancos continuam destacados; nada além da superfície muda. +- **O que NÃO negociar:** nenhum redesenho da seção (cards/carrossel/links/textos); hero e outras seções intactos; nenhuma cor nova; fail-closed inalterado (sem posts visíveis a seção continua sumindo — o teste e2e de contiguidade Prova→Problema não pode quebrar). +- **O que reavaliar:** a intenção recomenda "banda de fundo própria (A)" como meio de separação, mas o rascunho UI aprovado no gate (`separacao-visual-secao-conteudos-home-ui-draft.html`, Cena 1/3 recomendadas) mostra **banda + hairlines juntas** (`border-y border-zinc-300 bg-zinc-100`) — que é a opção C da própria intenção. O wireframe aprovado (`docs/campanha/wireframe-solla-1313.html`, seção 7) também desenha zona de cor própria com `border-top/bottom: 1px solid var(--line)`. O rascunho é o contrato visual do gate: recomendo **C**, não A. + +## Abordagem recomendada + +```mermaid +flowchart LR + P[home page.tsx] --> S[CampaignContentSection] + S --> B{bg + bordas no
} + B -->|C| BAND["border-y border-(--campaign-line)
bg-(--campaign-band)"] + BAND --> CARD[CampaignArticleCard
branco, inalterado] + BAND --> E2E[teste e2e: estilos computados
seção ≠ branco, borda top > 0, card branco] +``` + +**Opções consideradas:** + +- **A — banda de fundo própria, sem hairlines** (`bg-(--campaign-band)`): separa, mas a borda entre o branco da prova e a banda cinza é só contraste de tom; o desenho aprovado (wireframe seção 7 + draft Cena 1) inclui hairline. +- **B — hairlines mantendo fundo branco** (draft Cena 2, alternativa): mínimo, fiel ao wireframe no borda mas não na zona de cor; na home toda branca lê menos de longe (é o fallback da própria intenção). +- **C — banda + hairlines juntas** (draft Cena 1/3 recomendadas): o hairline em cima cria a fronteira exata Prova→Conteúdos; o hairline embaixo separa da faixa de prova social e a zona de cor dá o ritmo branco→banda→escuro previsto no wireframe. + +**Recomendação:** **C** — é literalmente o que o rascunho aprovado mostra e o que o wireframe seção 7 especifica; A e B são subconjuntos que lêem menos na home branca. +**Rejeitadas:** A e B como solução única (draft as marca como alternativa; a recomendada é banda + hairline). Qualquer redesenho de cards/tipografia/paleta — fora de escopo pelo anti-goal da intenção. + +### Componentes / mudanças + +- **`CampaignContentSection`** (`src/components/CampaignContentSection.tsx`): trocar a classe do `
` de `bg-white` para `border-y border-(--campaign-line) bg-(--campaign-band)`. É a única mudança de runtime. Tokens existentes (`--campaign-band: #ebe9e9`, `--campaign-line: rgb(0 0 0 / 12%)` em `styles.css:179-180`); `border-(--campaign-line)` reusa o padrão já usado em `CampaignArticleCard.tsx:24`. +- **Migration:** nenhuma (superfície pura, sem schema). +- **Access / Consent:** nenhum. +- **UI:** Impeccable B — encaixe de token em componente existente, sem shape novo. Contrastes conferidos sobre `#ebe9e9`: eyebrow `--pt-red` ≈ 4,9:1, copy `--campaign-muted` ≈ 4,5:1, título preto — ok. Cards já são `bg-white` + `border-(--campaign-line)` + `shadow-sm` e saltam na banda. O placeholder de capa (`bg-(--campaign-band)`) funde com a banda quando não há capa — estado de fallback de posts sem imagem (fora do escopo; produção usa capas reais). + +### Dados → forma (se aplicável) + +- Não aplicável — nenhum dado novo; a leitura da intenção ("Vou apresentar dados? Não") se mantém. + +## Fases verificáveis + +1. **Tracer** — mudança de classe no `
`; `pnpm exec tsc --noEmit` + lint. +2. **UI/teste** — asserções de estilo computado no teste e2e existente da seção (`tests/e2e/frontend.e2e.spec.ts`, describe serial "Campaign home content section"): dentro do teste full-state, após os checks do bento em desktop (1280px), conferir via `getComputedStyle` que (a) `contents` ≠ `rgb(255, 255, 255)` e ≠ `proof` (faixa de prova social), (b) `border-top-width` > 0 e `border-top-color` não transparente, (c) card `a[href^="/artigo/"]` dentro da seção é `rgb(255, 255, 255)` — cards saltam na banda. Sem screenshot diff (o repo não usa regressão visual). +3. **Gates** — `pnpm gate:fast` (ou ao menos tsc + lint + unit), depois `pnpm test:e2e` no describe afetado (serial, DB de teste do worktree), `pnpm build`, `pnpm format:check`, `knip`, `check:cycles`. + +## Rabbit holes / Não escopo (engenharia) + +- "Aproveita e muda o placeholder de capa para `--campaign-surface`/tom mais escuro para saltar na banda": melhora cosmética fora do aceite, e o placeholder é fallback (capa real vem do media). Deixar como está; registrar como débito se notado no /simplify. +- "Banda só no desktop, hairline no mobile" ou vice-versa: o draft trata mobile e desktop iguais; variantes não pedidas. +- Mexer na classe do card, no carrossel ou em `page.tsx`: explícito fora de escopo. + +## Riscos e mitigação + +- **Contiguidade Prova→Problema com seção oculta:** o teste existente (`frontend.e2e.spec.ts:524-528`) ainda valida gap ≤ 1px quando a seção some; a mudança não afeta o retorno `null` fail-closed (o `
` nem renderiza). Sem risco. +- **`--campaign-band` compartilhado com "Nossa caminhada" e placeholder de capa:** seções não adjacentes (flags fica abaixo do problema escuro); a intenção já mapeou e aceitou o ritmo. +- **Borda dupla no mobile carrossel:** dots/leitura usam `--campaign-muted`/`--pt-red`, visíveis sobre a banda; nada a ajustar. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (fronteira visível, nada além muda, fail-closed intacto) +- [x] Invariantes AGENTS/engineering-standards (sem schema/access/LGPD; identifiers em inglês; copy pt-BR intacta) +- [x] Testes previstos: asserções de estilo computado no e2e full-state existente (desktop; o trecho mobile do mesmo teste segue cobrindo o carrossel na banda) diff --git a/src/components/CampaignContentSection.tsx b/src/components/CampaignContentSection.tsx index ccc2a852..15f86020 100644 --- a/src/components/CampaignContentSection.tsx +++ b/src/components/CampaignContentSection.tsx @@ -49,7 +49,11 @@ export const CampaignContentSection = async () => { const [featured, ...rest] = cards return ( -
+
diff --git a/tests/e2e/frontend.e2e.spec.ts b/tests/e2e/frontend.e2e.spec.ts index d220af3c..6414c62e 100644 --- a/tests/e2e/frontend.e2e.spec.ts +++ b/tests/e2e/frontend.e2e.spec.ts @@ -631,6 +631,16 @@ test.describe('Campaign home content section', () => { await expect(firstCard.locator('h3')).toHaveText('E2e Artigo em destaque') await expect(firstCard.locator('span').last()).toContainText(categoryName) + // S5 — the section must read as a zone of its own: a banded background + // with a top hairline against the white proof strip, and white cards + // that pop against the band. Exact computed values pin the tokens + // (`--campaign-band` #ebe9e9, `--campaign-line` rgb(0 0 0 / 12%)) so a + // broken or removed token cannot silently pass. + await expect(section).toHaveCSS('background-color', 'rgb(235, 233, 233)') + await expect(section).toHaveCSS('border-top-width', '1px') + await expect(section).toHaveCSS('border-top-color', 'rgba(0, 0, 0, 0.12)') + await expect(firstCard).toHaveCSS('background-color', 'rgb(255, 255, 255)') + await featured.click() await expect(page).toHaveURL(new RegExp(`${featuredHref}$`)) await page.goBack() -- 2.45.2 From 5b50e1da71427ba81ffa9a265859d70dfbe6a0b5 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 18 Aug 2026 14:04:50 -0300 Subject: [PATCH 2/2] =?UTF-8?q?ci:=20retry=20=E2=80=94=20e2e(1)=20falhou?= =?UTF-8?q?=20no=20boot=20do=20servi=C3=A7o=20postgres=20(porta=205435=20o?= =?UTF-8?q?cupada=20por=20container=20vazado=20de=20CI=20anterior;=20remov?= =?UTF-8?q?ido)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 2.45.2