Home abre no agradecimento pelos 152.049 votos com board 1+3, trajetória, bandeiras do mandato, galeria de fotos e rodapé institucional; chrome novo (header home/interno, lockup de nome, menu mobile acessível) e /artigos//fotos com título compacto. As superfícies de campanha saem do ar (308 de /conteudos, /jingles e /cards), junto com newsletter, estúdio público de cards e UI da Central/jingles; download de foto e .ics perdem o 1313. Sem migration, sem Consent novo e sem apagar dados do CMS.
50 KiB
Impl: Site público pós-eleição — retirar a propaganda e agradecer os 152.049 votos (S47)
Status: aprovado (modo --auto) Atualizado em: 2026-10-21 Issue: #1442 Intenção: docs/plans/site-pos-eleicao-agradecimento.md Design UI: docs/plans/site-pos-eleicao-agradecimento-ui-design.html Appetite restante: herdado (~1–2 dias eng; corte explícito: sem redesenho das páginas internas além do título/shell, sem migration, sem asset novo) Self-score decision-quality: 5/5 — as 8 decisões caras têm opções e rejeitadas, o escopo cabe no appetite, os rabbit holes estão nomeados, os shells existentes são reusados e o aceite de produto permanece o norte.
Leitura da intenção
- Outcome: o site público deixa de ser peça de campanha — nenhuma superfície pública exibe 1313 como número eleitoral, pedido de voto, “candidato”, “propaganda eleitoral” ou CNPJ de campanha; a home abre com “Obrigado, Bahia — 152.049 votos de confiança” e segue como casa institucional do mandato (notícias, trajetória, bandeiras, fotos e selfie), com rodapé institucional.
- O que NÃO negociar:
- O número 152.049 é o literal TSE (totalização de 05/10/2026), não percentual, ranking ou dashboard.
- O logo visível é o lockup de nome (
jorge-solla-positivo/negativo), nuncamarca-*-completa/numero-*. - Sem migration, sem Consent novo, sem PII nova, sem apagar dados do CMS.
/fotos,/artigos,/privacidadee o acervo de fala continuam;/potenciale a Plenária ficam até 25/10 e saem depois (decisão de produto já assumida).- O domínio/canonical atual continua até OPS131; nada de
/campanha. - O design hi-fi aprovado é o alvo do port.
- O que reavaliar (hipóteses da intenção que o código desmente):
- O board atual é 1+4 com carrossel mobile e feeds; o design pede 1+3 editorial e sem carrossel obrigatório.
- O
/artigosatual usa hero em vermelho com imagem do globalhome; o design pede título compacto em creme — a imagem passa a ser OG, não hero. - O
/fotosatual tem header vermelho eArchivePhotoHerode campanha; o design pede header positivo em creme e título compacto. - O
CampaignFooterhoje é condicional (Jingles/Conteúdos/Fotos) e eleitoral; vira institucional com Fotos sempre visível. - O vídeo da trajetória é um Short 9:16 real; o 16:10 do gate é composição, não mídia existente.
Abordagem recomendada
flowchart LR
D[Design S47<br/>1280 + 390] --> SH[CampaignPageHeader<br/>+ CampaignMobileMenu]
SH --> HOME["(home)/page.tsx"]
HOME --> HERO[CampaignHero<br/>Obrigado, Bahia · 152.049]
HOME --> BOARD[CampaignContentSection<br/>1+3 editorial]
HOME --> STORY[CampaignStorySection]
HOME --> FLAGS[CampaignFlagsSection<br/>3×2 hairlines]
HOME --> PHOTOS[CampaignPhotoSection<br/>2 fotos + quote]
HOME --> EXT[Plenária · Selfie<br/>condicionais/fail-closed]
HOME --> FOOT[CampaignFooter<br/>institucional]
BOARD --> POSTS[getVisiblePosts + YouTube + Instagram]
BOARD --> SHARE[ContentShareButton<br/>S4 preservado]
PHOTOS --> ARCH[getApprovedArchivePhotoItems]
SH --> ART["/artigos + /fotos<br/>header + título creme"]
FOOT --> ART
R["/conteudos · /jingles · /cards"] --> RED[next.config.mjs<br/>308]
RED --> ART
RED --> HOME
Opções consideradas: A) editar os donos existentes (header/footer/hero/board/story) e portar o design na home + shell, com redirects das rotas de campanha; B) criar um “site do mandato” paralelo (novo shell/novas rotas); C) manter as páginas de campanha publicadas, só escondendo-as do menu.
Recomendação: A — é o único caminho que cabe em ~1,5 dia: reusa CampaignHero, CampaignContentSection, CampaignStorySection, CampaignFooter, CampaignPageHeader, CampaignLogoLink, ShareLinkHomeSection e SelfieSearchHomeSection, troca somente a composição/copy e remove as superfícies de campanha. Evita collection, migration e um segundo design system.
Rejeitadas: B porque duplica shell, cria uma segunda navegação e estoura o appetite (o site institucional completo é PUB2); C porque mantém as rotas acessíveis/indexáveis e o código de campanha vivo, falhando o aceite “saem do ar”.
Ordem final da home (alvo do port)
| # | Seção | Dono no código | Conteúdo/observação |
|---|---|---|---|
| 1 | Header | CampaignPageHeader (variant="home") |
Lockup negativo sobre vermelho; nav desktop Mandato · Bandeiras · Fotos; menu mobile acessível |
| 2 | Hero de agradecimento | CampaignHero (reescrito) |
Eyebrow Obrigado, Bahia; h1 152.049 / votos de confiança; copy do design; CTA Acompanhe o mandato → /artigos; identidade; retrato JOA00162.webp |
| 3 | Plenária (condicional) | ShareLinkHomeSection |
Mantida logo após o hero até 25/10; ausente por CMS/kill switch; não é CTA concorrente |
| 4 | Board editorial | CampaignContentSection (reescrito) |
1+3 em creme com hairlines; 4 itens (posts + YouTube + Instagram); sem carrossel mobile; estado vazio “Ainda não há publicações” |
| 5 | Trajetória | CampaignStorySection |
Nossa história / Saúde pública como escolha de vida; Short 9:16 existente; CTA “Assistir à trajetória” |
| 6 | Bandeiras | CampaignFlagsSection (novo) |
Bandeiras do mandato / Compromissos que orientam o trabalho; 3×2 desktop, 1×6 mobile, hairlines, sem cards flutuantes |
| 7 | Fotos | CampaignPhotoSection (novo) |
Perto da Bahia / O mandato em fotos; 2 fotos aprovadas + tile “Trabalho presente, memória viva.”; link /fotos; estado vazio “O álbum está sendo preparado” |
| 8 | Selfie (condicional) | SelfieSearchHomeSection |
Mantida após as fotos; só com álbum publicado + flag + foto aprovada (C243) |
| 9 | Rodapé | CampaignFooter (reescrito) |
Institucional: lockup, cargo/partido/estado, Navegue (Artigos · Fotos · Privacidade), Redes, linha legal; sem CTA/CNPJ/turno |
O design mostra 1–2–4–5–6–7–9; as seções 3 e 8 são exceções vivas do produto, condicionais e fail-closed, e não alteram a dobra.
Contrato visual (resumo do gate)
- Tokens/fontes:
--pt-red #a21c1c,--pt-red-dark #47130e,--pt-yellow #ffe607,--campaign-cream #fff8f2,--campaign-band #ebe9e9,--campaign-line rgb(0 0 0 / 12%),--campaign-muted #6c615e; Exo 2 (display) e Arimo (texto) já carregadas emfonts.ts/styles.css. Nenhuma fonte/paleta nova. - Geometria: conteúdo máximo 1152px; header desktop 88px, mobile 74px; hero desktop mínimo 668px, mobile 760px; um único CTA primário na dobra; board 7/12 + 5/12 no desktop e coluna única no mobile; bandeiras 3×2; fotos 530px no desktop; cantos 8–12px; sem sombra na página real.
- Estados: board vazio “Ainda não há publicações”; álbum vazio “O álbum está sendo preparado”; nunca buraco silencioso.
- A11y: foco C243 (3px amarelo + separador azul-escuro
#184e92) em superfícies claras; amarelo visível no escuro; menu mobile abre/fecha no trigger, fecha no X/link/fora/Escape, foca o primeiro link ao abrir e devolve o foco ao trigger; alt contextual;prefers-reduced-motionremove transições. - Proibido na superfície: 1313, pedido de voto, “candidato”, “propaganda eleitoral”, CNPJ eleitoral, data/turno, “Quero apoiar”, newsletter/captação, Central, cards eleitorais, jingles/rádio,
marca-*-completa,numero-*, ranking/percentual/dashboard.
Componentes / mudanças
CampaignPageHeader(src/components/CampaignPageHeader.tsx): dono único do topo público; ganhavariant: 'home' | 'internal',current?: 'artigos' | 'fotos'e mantémbadge?(share link). Home = fundo vermelho/lockup negativo/nav branca; interno = creme/lockup positivo/nav vermelha. Renderiza o logo no servidor e delega só o disclosure mobile ao cliente.CampaignMobileMenu(src/components/CampaignMobileMenu.tsx, novo, client): disclosure acessível do menu mobile; sem dialog/trap (não é modal), comaria-expanded,aria-controls,hidden, foco inicial/retorno e fechamento por Escape/clique fora/link.CampaignLogoLink(src/components/CampaignLogoLink.tsx): continua dono do crop do lockup; ganhatone: 'negative' | 'positive'epriority?; corrigearia-labelpara “Início — Jorge Solla” e alt “Jorge Solla — Deputado Federal”; positiva usa janela 160×58 desktop/128×46 mobile (img 209px; offsets −24,8/−32,6).CampaignFooter(src/components/CampaignFooter.tsx): vira o rodapé institucional; perdeshowJingles/showConteudos/showFotos;current?: 'artigos' | 'fotos'; Fotos sempre visível (a página do álbum tem estado vazio honesto); semQuero apoiar, CNPJ, propaganda ou turno.CampaignHero(src/components/CampaignHero.tsx): mesmo dono, conteúdo novo; constante localTHANK_YOU_VOTE_TOTAL = '152.049'; h1 comaria-labelcompleto; CTA único/artigos; retrato e composição do gate; nenhum “Quero apoiar”/1313.CampaignContentSection(src/components/CampaignContentSection.tsx): mantém o pipeline de posts + YouTube + Instagram e o share (S2/S3/S4); reescreve o layout para 1+3, remove o carrossel mobile e passa a renderizar o estado vazio em vez denull; limite 4.CampaignContentCard(src/components/CampaignContentCard.tsx): ganhavariant: 'featured' | 'compact'; compact é texto (sem capa) com kicker vermelho, título e “Ler notícia/abrir”; o share continua como ação secundária acessível.CampaignStorySection(src/components/CampaignStorySection.tsx): copy do design; mantém o iframeyoutube-nocookie9:16 existente.CampaignFlagsSection(src/components/CampaignFlagsSection.tsx, novo): seis bandeiras do design em<ol>com hairlines; substituiCampaignFlagCard/CampaignCarousele a copy eleitoral.CampaignPhotoSection(src/components/CampaignPhotoSection.tsx, novo): recebeArchivePhotoPublicItem[]; 2 fotos + quote tile; empty state; usathumbnailPath(proxy privado) e alt curado.(home)/page.tsx: nova composição; uma leitura degetApprovedArchivePhotoItems()serve fotos + gate da selfie (sem o matching facial, que só o álbum precisa); mantém Plenária/selfie condicionais; remove proof/problem/cards/sound/content-pieces/newsletter.publicSite(src/lib/publicSite.ts, novo): caminhos, links sociais oficiais e receita de foco compartilhados por header/footer/board; evita strings duplicadas.- Migration: nenhuma. Nenhuma collection/global/field é alterado.
- Access / Consent: nenhum novo. Remove-se o caminho da newsletter (action/schema/helper/chave
campanha-novidades); a linhaConsente as subscriptions existentes não são apagadas. - UI: Impeccable C; shape→craft→critique→polish com o
designer; shells existentes; sem novo design system.
Arquivos por área
Shell e páginas internas
| Arquivo | Ação | Detalhe |
|---|---|---|
src/lib/publicSite.ts |
criar | PUBLIC_PATHS, PUBLIC_SOCIAL_LINKS, PUBLIC_FOCUS; usado por header/footer/board |
src/components/CampaignLogoLink.tsx |
editar | tone, priority, crop positivo/negativo, alt/aria sem 1313 |
src/components/CampaignPageHeader.tsx |
reescrever | variantes home/internal, nav, current, badge; sem 1313 no alt/aria |
src/components/CampaignMobileMenu.tsx |
criar (client) | disclosure a11y completo |
src/components/CampaignFooter.tsx |
reescrever | institucional, sem flags, current, Fotos sempre |
src/app/(frontend)/artigos/layout.tsx |
editar | data-theme="campaign-site", CampaignPageHeader current="artigos", CampaignFooter current="artigos"; sai SiteHeader/Footer |
src/app/(frontend)/artigos/page.tsx |
editar | título creme “Mandato em movimento / Artigos e notícias”; generateMetadata com canonical /artigos e OG de home.image; manter grade de últimas notícias; remover hero/imagem/CTA |
src/app/(frontend)/fotos/page.tsx |
editar | header compartilhado; título creme “Memória do mandato / Fotos”; remover ArchivePhotoHero, flags de rodapé e hasPublishedJingles/hasPublishedContentPieces; manter busca/filtros/grade/lightbox/removal |
src/app/(frontend)/fotos/encontre/page.tsx |
editar | header compartilhado; h1 “Encontre você nas fotos” + descrição; remover flags/hasPublishedContentPieces; título sem 1313 |
src/app/(frontend)/fotos/[id]/midia/route.ts |
editar comentário | nome legível vem de downloadFilename; atualizar o literal do comentário |
src/lib/archivePhotoPublicCatalog.ts |
editar | DOWNLOAD_PREFIX = 'jorge-solla-foto' |
src/components/fotos/ArchivePhotoPageHeader.tsx |
deletar | substituído pelo header compartilhado |
src/components/fotos/ArchivePhotoHero.tsx |
deletar | substituído pelo título creme |
src/app/(frontend)/(home)/potencial/page.tsx |
editar | header/footer novos; remover flags de descoberta; manter estúdio até 25/10 |
src/components/shareLink/ShareLinkAnnouncement.tsx |
editar | remover props showJingles/showFotos; header/footer novos |
src/app/(frontend)/[type]/page.tsx |
editar | remover reads de jingles/fotos e props do anúncio |
src/utilities/seo.ts |
editar | SITE_METADATA_DEFAULTS.description → “Notícias, o mandato e a atuação do deputado Jorge Solla pela Bahia.” |
next.config.mjs |
editar | async redirects() com 308 |
Home
| Arquivo | Ação | Detalhe |
|---|---|---|
src/app/(frontend)/(home)/page.tsx |
reescrever composição | header, hero, plenária, board, story, flags, photos, selfie, footer; sem proof/problem/cards/sound/content-pieces/newsletter |
src/components/CampaignHero.tsx |
reescrever | hero de agradecimento; literal 152.049; CTA /artigos; retrato |
src/components/CampaignContentSection.tsx |
reescrever | 1+3, 4 itens, empty state, links fixos YouTube/Instagram, feeds/share preservados |
src/components/CampaignContentCard.tsx |
editar | variante compacta sem capa; share secundário |
src/components/CampaignContentCarousel.tsx |
deletar | design sem carrossel obrigatório |
src/components/CampaignStorySection.tsx |
editar copy | “Saúde pública como escolha de vida” / “Assistir à trajetória” |
src/components/CampaignFlagsSection.tsx |
criar | seis bandeiras do design; id="bandeiras" |
src/components/CampaignPhotoSection.tsx |
criar | 2 fotos + quote; empty state |
src/components/fotos/SelfieSearchHomeSection.tsx |
manter | posição após fotos; gate C243 intacto |
src/components/shareLink/ShareLinkHomeSection.tsx |
manter | posição após hero; S44 intacto |
src/app/(frontend)/(home)/layout.tsx |
manter | scroll container + MetaPixel PageView; atualizar comentário |
src/app/(frontend)/styles.css |
limpar | remover blocos mortos de hero/proof/problem/flags/sound/cards/newsletter; manter tokens e campaign-section-* compartilhados |
src/components/CampaignHashScroll.tsx |
comentário | #novidades → #bandeiras |
src/components/RefreshRouteOnSave.tsx |
comentário | referência ao anchor antigo |
Rotas, newsletter e órfãos
| Arquivo | Ação | Detalhe |
|---|---|---|
src/app/(frontend)/conteudos/** |
deletar | redirect 308 para /artigos |
src/app/(frontend)/jingles/** |
deletar | redirect 308 para / |
src/app/(frontend)/(home)/cards/** |
deletar | redirect 308 para / |
src/app/(frontend)/(home)/CampaignCardsSection.tsx |
deletar | seção de cards eleitorais |
src/components/CampaignCarousel.tsx, CampaignProblemCard.tsx, CampaignFlagCard.tsx |
deletar | sem consumidores após a home nova |
src/components/conteudos/** |
deletar | UI pública da Central; CMS//campanha ficam |
src/components/jingles/**, src/lib/radio.ts, src/lib/jingle.ts, src/utilities/jingleReads.ts |
deletar | UI/reads públicos de jingles/rádio; collection Jingle fica |
src/lib/contentPieceHomeSelection.ts, src/lib/contentPieceShare.ts, src/utilities/content/contentPieceThemeSearch.ts |
deletar se órfãos | rodar git grep -w antes; contentPieceReads mantém só o slug read usado por api/content-events |
src/components/cards/** (UI do estúdio) |
deletar somente a UI pública | manter cardCanvas.ts/cardCutout.ts (selfie e story canvas) e lib/cardModels/cardDownloadCounts/contentEvents (dashboard interno) |
src/components/CampaignNewsletterForm.tsx, src/app/(frontend)/(home)/CampaignNewsletterSection.tsx |
deletar | captura de campanha |
src/app/(frontend)/actions/submitCampaignNewsletter.ts, src/lib/schemas/campaignNewsletter.ts |
deletar | action/schema exclusivos |
src/lib/campaignConsentKeys.ts, src/utilities/campaignConsent.ts |
editar | remover chave/helper campanha-novidades; demais Consent intactos |
src/app/(frontend)/api/content-events/route.ts |
manter | eventos históricos; sem UI nova |
src/collections/**, src/globals/**, src/app/(campaign)/** |
não tocar | sem migration, sem PII, sem deleção de CMS |
Testes, scripts e docs
| Arquivo | Ação | Detalhe |
|---|---|---|
tests/unit/campaignHome.unit.spec.tsx |
reescrever | hero/head/board/flags/photos/footer; sem jingles/content pieces/cards/newsletter/pixel da page |
tests/unit/campaignHero.unit.spec.tsx |
reescrever | 152.049, CTA /artigos, sem 1313/candidato/Quero apoiar |
tests/unit/campaignPageHeader.unit.spec.tsx, campaignMobileMenu.unit.spec.tsx, campaignFooter.unit.spec.tsx |
criar | nav/current, menu (Escape/fora/foco), rodapé sem 1313/CNPJ |
tests/unit/campaignPhotoSection.unit.spec.tsx (opcional) |
criar | 0/1/2 fotos + quote + empty |
tests/unit/campaignCarousel.unit.spec.tsx, jingleHomeSection, jinglePlayer, radioPlayer, radio, jingle, campaignNewsletterLead, contentPieceHome* |
deletar | superfícies removidas |
tests/unit/siteMetadata.unit.spec.ts |
editar | default sem “campanha”/1313 |
tests/e2e/frontend.e2e.spec.ts |
reescrever o describe da home | hero/menu/board/flags/photos/footer/empty states/redirects; manter describes S1/S2/S3/S4 do board; apagar blocos de cards/newsletter/sound/carrossel |
tests/e2e/frontendFotos.e2e.spec.ts |
editar | header positivo + crop S36, nome jorge-solla-foto-<id>, seção de fotos da home, sem 1313 |
tests/e2e/frontendShareLink.e2e.spec.ts |
editar | ordem hero → plenaria → contents; footer/header novos; fixtures sem 1313 |
tests/e2e/frontendJingles.e2e.spec.ts, frontendConteudos.e2e.spec.ts, campaignNewsletter.e2e.spec.ts |
deletar | páginas/superfícies removidas |
scripts/lib/e2e-affected-manifest.mjs |
editar | remover specs/chaves curadas; mapear componentes do shell para frontend/frontendFotos/frontendShareLink; manter o never-zero |
tests/unit/e2eAffectedManifest.unit.spec.ts |
editar | pin da lista curada sem newsletter/jingles/conteudos |
tests/int/submitCampaignNewsletter.int.spec.ts |
deletar | action removida |
tests/int/jingle.int.spec.ts |
deletar/atualizar | leitura pública some; collection fica |
tests/int/contentPiece.int.spec.ts |
atualizar | collection/CMS fica; reads públicos removidos |
AGENTS-public.md |
editar | footer sempre Fotos; download jorge-solla-foto-<id> |
docs/changelog/2026-10-21-s47.md |
criar | entrada única da entrega |
docs/TECH-DEBT.md |
editar | remover referência ao submitCampaignNewsletter.ts (P6-J) |
DESIGN.md §7 |
conferir | a direção S47 já está registrada; não reescrever |
Decisões de engenharia
D1 — Destino de /conteudos, /jingles e /cards.
Opções: A) redirects() no next.config.mjs com 308 (/conteudos + /conteudos/:path* → /artigos; /jingles + /jingles/:path* → /; /cards → /) e apagar as rotas; B) manter arquivos finos com permanentRedirect; C) apagar e responder 404; D) manter publicadas fora do menu.
Recomendação: A — preserva links/SEO de URLs que já circularam, tira o código de campanha da árvore de render e não exige um layout/rota viva só para redirecionar. Query strings (/cards?model=...) são preservadas pelo redirect.
Rejeitadas: B mantém módulos/layouts de campanha vivos e o redirect só ocorre depois do render; C perde tráfego legítimo e indexa 404; D viola “saem do ar/do menu”. /potencial e /plenaria-vitoria não entram nessa decisão (ficam até 25/10).
D2 — CampaignFooter/CampaignPageHeader compartilhados.
Opções: A) evoluir os donos atuais para o shell institucional e atualizar todos os call sites; B) criar MandateFooter/MandateHeader novos e deixar os antigos para /potencial/share link; C) duplicar por página.
Recomendação: A — “edite o dono, não crie gêmeo”; /fotos, /artigos, /potencial e o anúncio de share link passam a usar o mesmo shell, e o comportamento eleitoral sai de todos de uma vez. Mantêm-se os nomes históricos CampaignFooter/CampaignPageHeader (renomear é cosmético e fica fora do appetite).
Rejeitadas: B cria dois shells com o mesmo contrato e mantém o código eleitoral vivo; C gera drift de nav/foco/alt em cada página. A variante visual mora em variant, não em arquivo novo.
D3 — Newsletter (UI + action/Consent/teste).
Opções: A) remover a seção, o form, a action, o schema, a chave/helper campanha-novidades e os testes; manter a linha Consent e as subscriptions existentes no CMS; B) manter o form com copy de mandato e novo Consent; C) manter o form desabilitado.
Recomendação: A — o consentimento existente foi dado para “novidades da campanha”; reaproveitá-lo para o mandato muda a finalidade sem revisão jurídica, e criar um novo Consent está fora do escopo (sem PII/Consent novo). A remoção também elimina o Lead de campanha; o Meta Pixel de PageView do layout permanece (tracking de site, não captura).
Rejeitadas: B exige novo texto/key e revisão LGPD; C deixa um formulário morto e falha o aceite. Consequências: campaignNewsletter.e2e, campaignNewsletterLead.unit, submitCampaignNewsletter.int saem; a lista curada do e2e é atualizada de propósito.
D4 — Escopo da remoção de órfãos vs knip.
Opções: A) apagar no mesmo PR toda a UI/helpers públicos que ficaram sem consumidor, mantendo os módulos compartilhados com /fotos/encontre, /potencial e /campanha; rodar knip e podar exports; B) deixar órfãos e afrouxar o knip; C) apagar as árvores inteiras de components/cards e components/conteudos.
Recomendação: A — knip é gate de CI. components/cards/cardCanvas.ts e cardCutout.ts alimentam a busca por selfie; sectionStoryCanvas usa downloadBlob; lib/cardModels/cardDownloadCounts/contentEvents alimentam o dashboard interno; utilities/content/* atende a Central do /campanha. Só morre a UI pública do funil de cards/Central/jingles.
Rejeitadas: B viola “dead code dies”; C quebra a selfie e o estúdio do /potencial. Regra de execução: git grep -w <symbol> antes de cada remoção e pnpm knip nu (sem --fix cego) no checkpoint.
D5 — Board: feeds+share ou 1+3 estrito do design. Opções: A) manter o pipeline (posts + YouTube + Instagram) e o share S4, portando a composição 1+3 e removendo o carrossel; B) 4 posts visíveis apenas, sem feeds/share; C) manter o bento 1+4 atual. Recomendação: A — o board continua fresco e preserva contratos S2/S3/S4; o 1+3/hairlines é a camada visual. O share fica como ação secundária (não é CTA de campanha) e o mobile empilha, como o design pede. Rejeitadas: B descarta feeds e quebra testes/contratos sem pedido de produto; C ignora o design e mantém o carrossel que ele removeu. Gatilho de revisão: se o share poluir o board editorial, desligá-lo apenas na home (o card de artigo continua compartilhável).
D6 — Selfie e Plenária fora do design.
Opções: A) manter ambas condicionais e fail-closed (Plenária após o hero até 25/10; selfie após as fotos); B) remover as duas agora; C) manter selfie e remover Plenária.
Recomendação: A — a intenção manda manter; a Plenária é 2º turno até 25/10 e tem kill switch por CMS; a selfie é feature do acervo (C243) com gate próprio. Nenhuma compete com o CTA único do hero.
Rejeitadas: B corta produto vivo antes do 2º turno e uma feature de fotos que não pertence a esta Issue; C corta uma superfície de 2º turno ainda no ar. As duas continuam com data-home-section e testes próprios; a saída delas é operacional (despublicar) e não código deste PR.
D7 — Metadados quando o global é CMS.
Opções: A) o código corrige fallbacks e literais de página (home default, /artigos, /fotos, /fotos/encontre) e uma nota operacional manda editar o global metadata + SiteSettings.headerTitle no admin; sem migration e sem sobrescrever CMS; B) migration para reescrever o global; C) override incondicional no código.
Recomendação: A — o global é dado editorial de produção; migration sobrescreveria copy humana e viola “sem migration”; override quebra a autoridade do CMS. O canonical continua metadata.URL até OPS131.
Rejeitadas: B e C pelos motivos acima. A verificação pós-deploy é obrigatória: curl em /, /artigos, /fotos e uma página [type]; se “1313” aparecer no <title>, é CMS e o operador corrige no admin.
D8 — Foto download sem 1313.
Opções: A) trocar DOWNLOAD_PREFIX para jorge-solla-foto e atualizar o comentário/teste; B) manter jorge-solla-1313-foto por “contrato”; C) criar alias para o nome antigo.
Recomendação: A — o nome é Content-Disposition/atributo download, não rota nem canonical; nenhum link externo depende dele; o caminho /fotos/<id>/midia permanece. O aceite proíbe 1313 na superfície pública, inclusive no arquivo baixado.
Rejeitadas: B mantém o número eleitoral visível; C não faz sentido para um filename de resposta. Atualizar AGENTS-public.md e o teste e2e que pinam o nome.
Decisões complementares (baratas, com gatilho)
- Vídeo da trajetória: manter o Short 9:16 real dentro do layout editorial; o frame 16:10 do gate é composição, não mídia. Gatilho: se chegar um corte horizontal oficial, portar o poster 16:10 e o play nesse PR.
- Título interno: portar o bloco compacto creme de
/artigos(“Mandato em movimento / Artigos e notícias”) e/fotos(“Memória do mandato / Fotos”); o corpo (grade de notícias/busca do álbum) fica.[type],/corte,/privacidadee/mandato-no-whatsappnão são redesenhados. HomePageglobal: a imagem deixa de ser hero e passa a ser a OG de/artigos; o global continua usado e não é apagado. Gatilho: se o editor quiser aposentar o global, é uma Issue de CMS separada.- Nomes
Campaign*: mantidos por histórico; renomear só quando houver um terceiro shell público. - Plenária/
potencial: saída após 25/10 é despublicar o share link e remover a rota/potencialnuma entrega futura; não neste PR.
Dados → forma
- Forma escolhida: um único número absoluto, 152.049, no hero, com algarismos tabulares e
aria-label="152.049 votos de confiança". O número é memória de confiança, não placar. - Por quê: é o único dado que a intenção quer apresentar; o contrato do design permite que ele domine a dobra, mas proíbe ranking, percentual estadual, série ou dashboard.
- Rejeitadas: percentual/ranking (convidam comparação e não são o produto), contador animado (ruído/motion, pouco sóbrio), campo no CMS (seria migration e o dado é um fato único). O literal vive numa constante local de
CampaignHerocom a fonte TSE no comentário; não acoplar ao artefato do 2º turno.
Fases verificáveis
-
Shell compartilhado — ~4h.
publicSite,CampaignLogoLink,CampaignPageHeader,CampaignMobileMenu,CampaignFooter; wire em/artigos,/fotos,/fotos/encontre,/potencialeShareLinkAnnouncement; apagarArchivePhotoPageHeader/ArchivePhotoHero; título/metadata de/artigose/fotos. Checkpoint: unit de header/menu/footer/logo;pnpm typecheck; inspeção 390/1280 do header positivo/negativo e do foco por teclado. -
Home — ~5h.
CampaignHero,CampaignContentSection(1+3 + empty),CampaignContentCardcompacto,CampaignStorySectioncopy,CampaignFlagsSection,CampaignPhotoSection,(home)/page.tsx; manter Plenária/selfie; remover proof/problem/cards/sound/content-pieces/newsletter da composição. Checkpoint: unitcampaignHome/campaignHero/campaignPhotoSection; home renderiza 0/1/2 fotos e board vazio; sem overflow em 320/390/1280; ordemhero → plenaria? → contents → story → flags → photos → selfie? → footer. -
Rotas, newsletter e órfãos — ~3h. Redirects 308; apagar
/conteudos,/jingles,/cards; apagar UI pública de Central/jingles/cards/newsletter; podar exports e testes; atualizar manifest/curadoria. Checkpoint:pnpm knip,pnpm check:cycles,pnpm test:unit -- e2eAffectedManifest,pnpm test:int; redirects testados commaxRedirects: 0(308 + location). -
Copy, SEO e docs — ~2h. Grep de 1313/“candidato”/“propaganda”/CNPJ; download filename; títulos
/fotos/encontre;SITE_METADATA_DEFAULTS; canonical/artigos;AGENTS-public.md; changelog; tech-debt. Checkpoint:pnpm gate:fast;pnpm format:check; e2e de home/fotos/artigos sem 1313 no header/hero/footer/título. -
Verificação e PR — ~2h.
pnpm gate:ci(mesma cascata do PR, inclui build/e2e selecionado); rodar o conjunto afetado de e2e; QA visual 390/1280 contra o gate; crítica dodesigner; revisão de teclado (menu, Escape, foco, links do rodapé); abrir o PR viapnpm push. Checkpoint: PR ready com a descrição do aceite, evidências de 390/1280 e a nota operacional dos metadados.
Rabbit holes / Não escopo (engenharia)
- Redesenhar
[type],/corte,/privacidade,/mandato-no-whatsappou o abaixo-assinado (S48). - Criar marca nova, paleta/fonte nova ou “site institucional completo” (PUB2).
- Apagar posts, fotos, jingles, peças, cards ou Consents do CMS; “limpar” o banco.
- Migration para metadados, Consent ou qualquer schema;
pushcontra DB remoto. - Mexer em
/campanha, nas roles, nos dashboards internos ou na Central interna. - Reintroduzir newsletter/captação com copy de mandato.
- Ranking/percentual/dashboard de votos no hero.
- Novo player, novo carrossel, novo card ou novo asset de vídeo/foto.
- Refatorar o share link/Plenária além do header/footer.
- Remover
/potencialou a Plenária antes de 25/10. - Lint global proibindo “1313” (falsos positivos em domínio/assets); a verificação é grep classificado + testes de superfície.
- Mapa de redirect para todo o acervo antigo; só as três rotas de campanha.
Riscos e mitigação
| Risco | Prob. | Impacto | Mitigação |
|---|---|---|---|
Header/footer compartilhados quebrarem /potencial ou o anúncio do share link |
Média | Alto | Atualizar todos os call sites no mesmo PR; typecheck; e2e frontendShareLink/frontendFotos; manter badge e current |
Remoção de órfãos quebrar selfie/potencial (canvas/cutout compartilhados) |
Média | Alto | git grep -w antes de apagar; manter cardCanvas/cardCutout e lib/cardModels/cardDownloadCounts; knip nu |
Global metadata/SiteSettings.headerTitle continuarem com 1313 em produção |
Alta | Alto | Nota operacional com campos exatos; edição no admin antes do anúncio; curl pós-deploy; sem migration/override |
| Board/photos vazios ou feed fora do ar deixarem buraco | Média | Médio | Empty states do design; feeds fail-soft já existentes; teste de board vazio e álbum vazio |
| Menu mobile com foco/Escape errado | Média | Médio | Unit do menu + e2e de teclado; foco inicial/retorno; aria-expanded/aria-controls; hidden |
| Redirect/canonical errado ou URL antiga indexada | Baixa | Médio | 308 testado; canonical /artigos//fotos; metadata.URL intacto; sem sitemap |
| Lista curada/manifest com spec inexistente derrubar o CI | Alta | Médio | Atualizar E2E_CURATED_SPECS, E2E_AFFECTED_MANIFEST e o pin unit no mesmo commit |
| Crop do lockup positivo cortar a tinta | Média | Médio | Teste com sharp no /fotos 1280/390; janela 160×58/128×46; ajustar offsets se preciso |
| Port divergir do design (story 9:16, share no board) | Média | Baixo | Desvios documentados; crítica do designer; gatilhos de revisão |
| 152.049 corrigido pelo TSE | Baixa | Baixo | Constante única no hero; troca de uma linha |
Nota operacional (sem migration)
Executar no admin de produção antes/junto do anúncio; o código não sobrescreve CMS:
- Configurações → Metadados: revisar
title,description,keywords,openGraph.descriptionetwitter.description; remover “1313”, “candidato”, “campanha eleitoral”, “1º turno”, “propaganda”. - Configurações → Site:
headerTitle= “Jorge Solla”; conferirsocialLinks(Instagram/YouTube/Facebook oficiais). - Links de compartilhamento: revisar títulos/descrições publicados (incluindo
plenaria-vitoria); despublicar a Plenária após 25/10. metadata.URL: manter o canonical atual até o cutover OPS131; não alterar neste PR.HomePage.image: permanece como OG de/artigos; nenhuma edição obrigatória.- Consent
campanha-novidadese subscriptions históricas: não apagar; não há novo Consent. Se o jurídico quiser purgar, é processo LGPD separado. - Pós-deploy:
curlem/,/artigos,/fotose uma página[type]; se “1313” aparecer em título/descrição, é dado do CMS e o operador corrige no admin. - Após 25/10: despublicar o share link da Plenária e abrir a remoção de
/potencialem entrega própria.
Aceite de engenharia
- Aceite de produto da intenção coberto: home de agradecimento com 152.049; identidade “Jorge Solla — Deputado Federal · PT · Bahia”; sem 1313/pedido de voto/candidato/propaganda/CNPJ em header, hero, board, story, flags, photos, footer,
/artigose/fotos; seções de campanha removidas; rodapé institucional;/fotos,/artigos,/privacidadee acervo de fala intactos;/potencial/Plenária até 25/10. - Invariantes: sem migration, sem Consent/PII novo, sem apagar CMS, sem
/campanha;isPostVisible/posts intocados; canonical atual preservado;CampaignFooter/CampaignPageHeadereditados no dono, sem gêmeo. - Design portado: ordem 1–9, 1152px, hero 88+668/74+760, board 1+3, flags 3×2 hairlines, fotos 530px, rodapé 3 colunas, um CTA primário, empty states com a copy do gate.
- A11y: menu mobile (abre/fecha/X/link/fora/Escape/foco), foco C243, alvos ≥44px, alt do lockup “Jorge Solla — Deputado Federal”, headings h1→h2→h3,
prefers-reduced-motion, share acessível. - SEO: títulos/descrições de
/artigos,/fotos,/fotos/encontree default sem 1313; canonical/artigos//fotos; redirects 308 testados; nota operacional do global entregue. - Gates:
pnpm gate:fast,pnpm knip,pnpm check:cycles,pnpm format:check,pnpm test:int,pnpm gate:cie e2e afetado (frontend,frontendFotos,frontendShareLink,campaignHomePixel) verdes. - Manifest/curadoria: sem spec inexistente;
frontendJingles/frontendConteudos/campaignNewsletterremovidos da lista congelada de propósito; componentes do shell mapeados para os specs certos. - Órfãos: knip sem arquivo/export pendente;
styles.csssem blocos mortos;HomePageglobal, collections e dados históricos preservados. - Docs:
AGENTS-public.mdatualizado (Fotos sempre, filename),docs/changelog/2026-10-21-s47.md, tech-debt sem referência à action removida;DESIGN.md§7 conferido. - Visual: 390/1280 contra o gate; sem overflow em 320/390/1280; crítica final do
designerregistrada no PR.