S1 — Seção de conteúdos na home de campanha — Artigos #29

Merged
fsolla merged 4 commits from S1-secao-de-conteudos-na-home-de-campanha-artigos into main 2026-08-18 05:07:27 +00:00
Owner

Closes #17

S1 — Seção de conteúdos na home de campanha (artigos)

Bento 1+4 no desktop e carrossel de 1 conteúdo por tela (dots + contador) no mobile, na 3ª posição da home, lendo o post collection com isPostVisible fail-closed (tag hidden eleitoral nunca vaza; sem posts visíveis a seção some inteira). Zero schema/migration/Consent; home estática revalidada pelo mecanismo posts existente.

Arquitetura

  • CampaignContentSection (server): header "Acompanhe de perto" + bento desktop + carrossel mobile; oculta no vazio.
  • CampaignArticleCard (presentacional): badge de tipo, capa 16:9, título, meta "há X · categoria" (relativa < 48h, absoluta além — nunca mente congelada na ISR); card inteiro é link canônico.
  • CampaignContentCarousel (client): snap 1/tela, foco + setas no track, inert nos slides inativos, aria-live no contador, dots ≥3:1.
  • formatRelativePostDate + POST_TYPE_BADGE_LABELS em src/utilities/posts.ts (unit-testados).
  • Home vira SSG com tag posts; export const revalidate foi testado e revertido (Next 15.4 prod: trava o bust por tag).

Decisões-chave (rejeitadas no impl plan)

  • Carrossel próprio em vez de 3ª variant no CampaignCarousel (pinado a Penpot/e2e; jobs diferentes).
  • Badge por tipo (decisão A da intenção: feed misto) — aprovado no gate.
  • formatRelativePostDate não reusa formatRelativeAge (contratos divergentes — refusal documentada).

Verificação

  • Unit 2282 + int 791 verdes; gate local completo (ci-pr mirror) verde.
  • e2e frontend 10/10 em dev e prod (E2E_PROD=1): empty-state, full-state desktop/mobile, fail-closed com tag oculta, click-through, teclado, swipe, dots; criação via REST com hooks reais; polling positivo pós-revalidate (ISR serve stale uma vez sob carga); about:blank antes do cleanup (prefetch de links apagados).
  • Suíte completa prod: 169-170 passed com flakes ambientais de carga em specs de campanha (diferentes a cada run, nenhum no código tocado).
  • /simplify 3 reviewers (engenharia/UX/produto): P1 de a11y e o congelamento da label relativa corrigidos; P2 absorvidos; débitos deferidos documentados no impl plan.
  • Impl plan: docs/plans/secao-conteudos-home-artigos-impl.md · changelog: docs/changelog/2026-08-18-s1.md
Closes #17 ## S1 — Seção de conteúdos na home de campanha (artigos) Bento 1+4 no desktop e carrossel de 1 conteúdo por tela (dots + contador) no mobile, na 3ª posição da home, lendo o `post` collection com `isPostVisible` fail-closed (tag `hidden` eleitoral nunca vaza; sem posts visíveis a seção some inteira). Zero schema/migration/Consent; home estática revalidada pelo mecanismo `posts` existente. ### Arquitetura - **`CampaignContentSection`** (server): header "Acompanhe de perto" + bento desktop + carrossel mobile; oculta no vazio. - **`CampaignArticleCard`** (presentacional): badge de tipo, capa 16:9, título, meta "há X · categoria" (relativa < 48h, absoluta além — nunca mente congelada na ISR); card inteiro é link canônico. - **`CampaignContentCarousel`** (client): snap 1/tela, foco + setas no track, `inert` nos slides inativos, `aria-live` no contador, dots ≥3:1. - **`formatRelativePostDate`** + `POST_TYPE_BADGE_LABELS` em `src/utilities/posts.ts` (unit-testados). - Home vira SSG com tag `posts`; `export const revalidate` foi testado e **revertido** (Next 15.4 prod: trava o bust por tag). ### Decisões-chave (rejeitadas no impl plan) - Carrossel próprio em vez de 3ª variant no `CampaignCarousel` (pinado a Penpot/e2e; jobs diferentes). - Badge por tipo (decisão A da intenção: feed misto) — aprovado no gate. - `formatRelativePostDate` não reusa `formatRelativeAge` (contratos divergentes — refusal documentada). ### Verificação - Unit 2282 + int 791 verdes; gate local completo (ci-pr mirror) verde. - e2e frontend 10/10 em dev e prod (`E2E_PROD=1`): empty-state, full-state desktop/mobile, fail-closed com tag oculta, click-through, teclado, swipe, dots; criação via REST com hooks reais; polling positivo pós-revalidate (ISR serve stale uma vez sob carga); `about:blank` antes do cleanup (prefetch de links apagados). - Suíte completa prod: 169-170 passed com flakes ambientais de carga em specs de campanha (diferentes a cada run, nenhum no código tocado). - `/simplify` 3 reviewers (engenharia/UX/produto): P1 de a11y e o congelamento da label relativa corrigidos; P2 absorvidos; débitos deferidos documentados no impl plan. - Impl plan: `docs/plans/secao-conteudos-home-artigos-impl.md` · changelog: `docs/changelog/2026-08-18-s1.md`
fsolla added 4 commits 2026-08-18 05:00:47 +00:00
Bento 1+4 no desktop e carrossel 1/tela no mobile (~390px) lendo o post
collection com isPostVisible fail-closed; seção some inteira sem posts
visíveis. Zero migration/Consent; home vira ISR com tag posts +
revalidate=300 (label relativa não congela).
Sob a suíte completa em paralelo, a regeneração pós-revalidate às vezes
servia a página velha uma vez; o DOM carregado nunca refrescava e o assert
de cleanup falhava (1 flaky no gate). Requisição GET de warm-up entre o
revalidate e o goto torna o estado determinístico.
O warm-up único ainda deixava uma janela de stale-serve sob carga (a
regeneração ISR não completava entre o GET e o goto, e o DOM carregado
nunca refrescava — flake no cleanup sob a suíte paralela). Polling do
estado esperado no HTML do servidor (12x1s) antes do goto torna o
empty-state, o full-state e o cleanup determinísticos.
fix(S1): revert revalidate=300 — trava o bust por tag no Next 15.4 prod
All checks were successful
CI (PR) / e2e (2) (pull_request) Successful in 4m9s
CI (PR) / e2e (1) (pull_request) Successful in 4m32s
CI (PR) / checks (pull_request) Successful in 2s
Promote plan-issue Related on main merge / plan-issue-ready (pull_request) Has been skipped
Main merge flips claimed issues to done + in-prod / issue-done (pull_request) Successful in 13s
Agent PR Ready + auto-merge / ready-automerge (pull_request) Has been skipped
Agent pool supervisor / tick (pull_request) Successful in 32s
CI (PR) / docs-guards (pull_request) Successful in 7s
CI (PR) / plans-only-closes (pull_request) Successful in 7s
CI (PR) / scope (pull_request) Successful in 32s
CI (PR) / lint (pull_request) Successful in 40s
CI (PR) / format (pull_request) Successful in 49s
CI (PR) / knip (pull_request) Successful in 32s
CI (PR) / typecheck (pull_request) Successful in 31s
CI (PR) / unit (pull_request) Successful in 45s
CI (PR) / cycles (pull_request) Successful in 34s
CI (PR) / int (pull_request) Successful in 2m5s
CI (PR) / build (pull_request) Successful in 2m38s
b46cfed889
Medido em probe: com export const revalidate, revalidateTag('posts') para
de regenerar a home (a rota /artigos, sem o export, convergia; a home
ficava stale para sempre). Remove o export e resolve o congelamento da
label relativa por threshold: relativa < 48h, data absoluta além (nunca
mente congelada). Spec: polling positivo do HTML da home pós-revalidate
(ISR serve stale uma vez sob carga) e about:blank antes do cleanup
(prefetch prod de links apagados = 404 no guard).
fsolla merged commit beea467816 into main 2026-08-18 05:07:27 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: fsolla/teqo#29
No description provided.