Pular para o conteúdo principal
← Voltar ao changelog

Changelog - 28/06/2026

Semana de 22/06 a 28/06, na branch main-7k do front-web-base.

  • O baseline de boot do Google Consent Mode v2 foi invertido pra granted (16bf59fee). Isto reverte o comportamento descrito no changelog de 23/04/2026. A política antiga emitia gtag('consent','default', { ...tudo denied, wait_for_update: 500 }) e só depois, se houvesse cookie salvo, aplicava um update. A nova emite gtag('consent','default', { ...tudo granted }) sempre, sem wait_for_update. O único caminho que ainda rebaixa sinais é cookieConsentCosmeticOnly === false e cookie salvo indicando rejeição total, caso em que o initGTM dispara um update imediato pra denied. Matriz implementada: cosmético truegranted (qualquer cookie ignorado); cosmético false sem cookie ou com aceite → granted; cosmético false com rejeição total → default granted + update imediato pra denied. Arquivos: app/utils/metrics/consent.ts (injectConsentDefaults com opts.applyDenied) e app/utils/metrics/gtm.ts (decide applyDenied = !cosmetic && userRejectedAll), com 4 casos de teste. A justificativa registrada no commit é paridade com o app Nuxt legado, que nunca gateava tracking, e a eliminação da janela cega de 500ms do wait_for_update, apontada como causa suspeita da queda de performance em Google Ads e Meta Ads após a migração.
  • cookieConsentCosmeticOnly passa a ter default true no base (be0518d91). Antes o default global era false, então brand que não declarasse a flag honrava a rejeição do usuário. As 7 brands que já declaravam true seguem idênticas; as 8 que herdavam false (casateste-com, donald-bet-br, pb-bet, ph-state77-com, pt-state77-com, rj-bet, state77-com, x2b-bet) passaram a declarar false explicitamente no próprio override — comportamento preservado bit a bit, com o ganho de que cada override é agora a fonte autoritativa da política de consent daquela brand, sem depender do default do base.
  • Nota de revisão pendente: as duas mudanças acima descrevem o que o código faz hoje. A política resultante — consent concedido por padrão, tratamento cosmético como default e ausência de botão de rejeição total no banner — ainda depende de confirmação do responsável por LGPD antes de ser publicada como política oficial. Registrado aqui como fato técnico, não como posição de conformidade.
  • Eco do gtag duplicando eventos do dataLayer corrigido em ea9b09a68.

Resiliência de navegação no Safari

  • Fog-of-war de descoberta de rota desligada (5f5640b7f): os logs de Cloudflare da betpontobet (Safari) mostraram que a request que mais falha é /__manifest?paths=..., a descoberta lazy de rotas do React Router 7 — 16 das falhas amostradas, todas "Network connection lost" com outcome=ok no worker, ou seja, o Safari derrubando a conexão no meio da resposta em navegação rápida. Páginas densas em links, como o lobby de cassino, disparam um /__manifest com 15+ paths, o que explica o cassino reproduzir mais. routeDiscovery: { mode: "initial" } manda o manifest completo no documento inicial, eliminando as requests e um round-trip de navegação; os módulos de rota seguem code-split.
  • Recuperação transparente em três camadas979b34850 faz retry de falhas transitórias na camada de fetch; 0dbf409c0 cobre o single-fetch (.data) com um reload de documento inteiro guardado por sessionStorage no ErrorBoundary, mais um try/catch no topo do fetch handler de workers/app.ts (antes, throw não tratado no SSR virava 500 cru sem log, uma falha estrutural de observabilidade); 3d7280287 move o backstop pra dentro do ErrorPage e substitui a tentativa anterior, que vivia só no ErrorBoundary do root: como o _layout tem ErrorBoundary próprio e o React Router entrega o erro ao boundary mais próximo, erro de loader em rota interna caía no _layout e o backstop nunca disparava. O ErrorPage é renderizado pelos dois boundaries. Não recarrega em 404/500 real.

Performance da home e isolamento de fontes

  • Pass de PSI/Core Web Vitals na home (cf38712b1): novo scheduleThirdPartyDeferred (idle do browser, ou primeira interação, ou timeout de segurança) tira Pendo, Zendesk, Hotjar, Clarity, Incognia e o selo Reclame Aqui do caminho crítico do load, mantendo GTM e pixels de conversão eager pra não perder evento. O banner da home passa a renderizar um <picture> com <source> mobile capado em 400px, então telefone nunca baixa banner de desktop independente do DPR, mais quality configurável (default 70, era 80 global) propagado pro base e pros overrides. O preload de thumbnail .mp4 de card de jogo vira viewport-gated por IntersectionObserver em useCustomThumb — cerca de 3,6 MB de vídeo off-screen deixa de baixar no load inicial. E o root troca o script de escape-hatch init2 pelo init4: o init2 recarregava a página inteira em toda visita fresca ou lenta pelo trigger duration>200 de /api/clear-cache — é o "redirect de 32s" que o PSI apontava, o double-load confirmado em trace de DevTools. O init4 é inerte por padrão.
  • Fontes de brand isoladas por override (68ebd706b): ~/config/theme/font-preloads e ~/styles/brand-fonts.css?url passam a ser brand-resolvidos como qualquer import ~/. O base traz Montserrat; a donald-bet-br sobrescreve os dois arquivos com Inter, então os woff2 de Inter deixam de entrar no bundle das outras brands. O resolveId do brandOverridesPlugin passou a tirar a query (?url) antes do lookup de override e reanexá-la depois, pra imports ?url resolverem tanto no override quanto no base. O __BRAND_KEY__ do define do Vite ficou sem uso e saiu.

Bolão 7K — API real, redesign do card e depois desligado

  • A API real entrou e o mock saiu (d6b27670c): a API de jogos do dia é a mesma API coreana usada pelo sweepstakes, então o service passou a reusar SWEEPSTAKES_API_URL e SWEEPSTAKES_BRAND_ID e a chamar GET /games/sweepstakes/daily-games com header brand-id. ApiMatch foi ajustado pro shape real (camelCase, isGameOver, startDate sem timezone) e Match.gameStarted virou gameOver.
  • Card de vitrine na home do 7k, iterado cerca de 15 vezes na semana58ff56d62 e c133ea5f0 criam o card acima de torneios, 7d2659729 o move pra acima de "Cassino ao vivo", 1512c0193 acerta o cabeçalho conforme o doc, 6c84c5a47 troca o CTA pelo CtaButton padrão da casa, a5412db7f renomeia pra "Dê seu palpite" e adiciona um segundo CTA "Apostar agora" (95a75d8f1 restila esse CTA e trata jogo expirado por horário), f9a55533c destaca o próximo jogo e 5b379e5e9 remove esse destaque, 1d1e5f4b7 põe borda em gradiente, 9d77e47a3 monta layout de 2 painéis no desktop, 189e94788 e 68e0deae9 tratam clique e hover das partidas.
  • Gateado por feature flag e então desativado na homeb49605873 faz o card respeitar a mesma flag de runtime feBolao7k que já controla menu lateral, agregador e bottom nav (antes dependia só do kill-switch de build-time bolaoConfig.enabled); dois dias depois, e6992b0c6 desliga o card da home via config, mantendo as superfícies de nav e sidebar. f121f63a6 instrumenta todos os CTAs com data-pendo-id. No modal de ranking do MatchX, o título "Ranking Geral" foi movido pro header e revertido no mesmo dia (0d88ddac1, 4cfbabc79, a71b27496).

Analytics e tracking

  • Pendo com tagging estruturado (3341fdd37): usePendo anexa o user.id numérico como metadata visitor.user_id quando autenticado; o pendo entra na matriz de destinos do registry de analytics com adapter pros eventos de conversão sign_up, withdraw e deposit_confirmed (o login segue só pelo usePendo pra não dupla-contar); o CtaButton passa a exigir data-element-id explícito em todo CTA, sem fallback implícito pro id do slot, pra o vocabulário ficar intencional; e navbar, sidebar, home e hub de cassino são tagueados com vocabulário neutro prefixado por área. Complementos: 5d08445da deriva os ids de card de jogo e quick-access de fonte estável e 8bdcc327f tagueia as setas dos carrosséis.
  • Container GTM secundário do 7k revertido (80044def8) — encerra a linha aberta na semana anterior. 583ce5988 habilita Pendo na betpontobet-bet-br.
  • AppsFlyer da vera-bet-br migrado pro proxy multibrand (68860b97c): o s2sUrl sai do endpoint dedicado da brand pro proxy multibrand e entra o campo novo AppsFlyerConfig.brandSlug. Quando ele está setado, a rota app/routes/api/tracking/appsflyer.ts injeta quatro headers derivados no servidor — x-brand, x-api-key (de APPSFLYER_API_KEY), x-idempotency-key (UUID v4 por chamada) e x-request-timestamp — e devolve 503 quando a chave falta, falhando alto em vez de deixar escapar chamada não autenticada. Brands sem brandSlug seguem no contrato payload-only, sem mudança na superfície do client.

Inbox, busca e conteúdo

  • Notificações do inbox renderizam HTML sanitizado7b447039a cria a flag inboxRenderHtml, b5e43b86f renderiza HTML sanitizado nos previews e b93006781 trata o título HTML vindo do Smartico, removendo <style> e <script> embutidos.
  • Jornada de busca inline-enter estendida a todas as brands (85b0560db): o trigger global não navega mais no foco nem enquanto se digita — a digitação preenche um buffer local e só o Enter (com 2+ caracteres) semeia a store e navega pra /buscar, onde os resultados de cassino renderizam em grade cheia (perPage 60). Estava atrás de uma flag searchJourney exclusiva da donald; como override de features.ts é substituição de arquivo e não deep-merge, um default no base não alcançaria brands com override próprio, então a flag foi removida e o comportamento virou universal.
  • Busca deixa de pesquisar termo velho (108fd101d): na montagem de /buscar, o efeito lastResolvedTerm → URL rodava uma vez com um termo reidratado do sessionStorage; ecoar esse termo velho pra ?q= — combinado com o efeito URL→store consumindo prematuramente o guard booleano de update interno — disparava doSearch(termoVelho) e sobrescrevia a busca que o usuário pediu ("digitei X, veio Y", que parecia cache no ambiente publicado). useSearchParamSync passa a pular o mount run e useSearchTriggerHandlers chama clear() antes de setTerm() no Enter.
  • Detalhe de post do WordPress degrada em vez de derrubar a página (defefc196): os três fetchers de contents.server.ts chamavam res.json() fora do .catch, que só cobre o setup da conexão — se o WP responde 200 e derruba a conexão no meio do body, o res.json() rejeita e propaga até o loader, virando 500 no worker. Amplificador: buildPostDetail chamava fetchAllAreaPosts, que paginava a área inteira (até 50 fetches sequenciais) só pra montar um rail de 8 itens — 50 vezes mais chance de uma leitura cair. As três leituras de body foram guardadas com try/catch + Array.isArray, então falha vira lista vazia: o rail desaparece e a página carrega.

Esportes

  • Iframe da First descarregado no unmount pra parar OOM (8d138966d): a First roda uma SPA pesada de odds ao vivo em iframe cross-origin. Sair de /esportes apenas desanexava o iframe, e a reclamação assíncrona do frame pelo WebKit ficava atrás do próximo mount, então alternar Esportes ↔ Cassino empilhava documentos pesados até o processo WebContent bater o limite de memória do iOS e ser morto — aba branca ou recarregada, reportada como "500". Confirmado por crash report de simulador (1,1 GB de WebKit Malloc). O SportsIframeFirst agora navega o iframe pra about:blank no unmount via ref persistida, liberando heap, DOM, websockets e timeline de animação de forma determinística. c095f8e18 mantém o iframe em altura cheia quando o teclado do iOS empurra o viewport.
  • Sportsbook rogue portado pra main-fix (07de21ea7): delta de 41 arquivos vindo da linhagem main-no-cache-sports, aplicado 3-way sem nada do experimento "sem cache" — provider nativo rogue (SportsRogue.tsx, rogue.server.ts, rotas /api/sports/rogue-*), @cactus-agents/sports de ^0.5.0 pra ^0.6.0, useSdkChromeOffsets, useSportsConfig, refactor de sports-seo e overrides de betpontobet e vera.
  • Proxy SSE da rogue streamado chunk a chunk (3aae4f9cb): o browser fecha o SSE constantemente (reconexão, navegação, troca de seleção no betslip) e o upstream dropa conexão idle; sem tratamento, o undici lançava TypeError: terminated como rejeição não tratada. O body upstream é re-streamado manualmente, engolindo o abort como fim de stream esperado, e o fetch upstream é amarrado ao request.signal pra desconexão de client abortar upstream (499). 49fc4b972 adiciona deep-links de sidebar da rogue para pb-bet.

Gamificação e layout

  • Contador de tempo restante volta nas missões (69728b90c): contador HH:MM:SS (com prefixo de dias quando ≥24h) em missões ativas com prazo futuro, no card e no modal — port do contador do legado Vue, perdido na migração. Reusa o useCountdown extraído de tournament-internals pra app/hooks/useCountdown.ts (o módulo antigo reexporta, importadores intactos), com MissionCountdownBadge e o helper puro shouldShowMissionCountdown. Cor via token text-primary. Gateado por gamificationMissionCountdown (default off, ligado em 7k, cassino e vera).
  • Descrição estruturada de torneio recuperada de JSON inválido (a3efbfcea): o CRM envia description como objeto JSON digitado à mão, com aspas duplas internas não escapadas no texto de termos, o que faz o JSON.parse estrito lançar e derrubar o payload estruturado inteiro. Entra um fallback ancorado em chave que extrai cada campo preservando aspas internas e newlines verbatim, com teste de regressão. 2263cb7f1 e 1a0c89855 completam o parser no lado da donald.
  • Scroll-lock de fundo opt-in enquanto overlay está aberto (9470aab36), gateado por lockBackgroundScrollOnOverlays (default false, só a donald liga). edb9fe38e restringe o drag a header em sheets no modo snap, pra clique em botão não ser engolido.
  • Label discreto de versão de build no footer (fa7619da2): mostra Versão: {BUILD_ID} (hash de 12 caracteres, de ~/utils/build-id) nos dois footers, pra dar visibilidade da versão em produção pra suporte e debug, via t("footer.version", { defaultValue: … }) — tradução pelo core depois, sem exigir release agora.

Core / SDK

  • @cactus-agents/payments (add8870): resolução de variação de método por match de prefixo mais longo, consumido no base por 006925d74.
  • @cactus-agents/payments + accounts + i18n (db5eb6f): statuses de pagamento v3 em inglês e método de wallet-redirect — o backend migrou de PT pra EN nessa semana.

Brand Overrides

  • donald-bet-br segue como o maior volume da semana. Ajustes de brand em cc5ba58e8 (fonte Inter, remoção de fade, itens de menu, header in-game). Footer: full-width abaixo da row com cap de altura da sidebar (b0ddbd8aa), remoção de copyright (f38cd661c) e da linha "Feito com ❤" (556621df9), links de crash e slots repontados (f3e32f367) e o link de Ouvidoria reposicionado e em caixa alta (173d8c4f6, d6849b832, 51b2f6ac5). Header: badge de notificação do avatar deixa de ser cortado (8102360ec, e9a0d396d). O drawer de pré-jogo ganha launch em tela cheia in-place (ba04ba03d), fechando o fluxo aberto na semana anterior. Recentes: card com dica "+" (2c4063f8b), largura casando com os carrosséis de categoria (5989b35f6), label "Jogos recentes" (4455776c8) e row oculta quando vazia (661b0a6df). Também recompensas com valor entre chaves (c181964cd, 43f6ce162), "Participando" como botão de sucesso quando inscrito (612d24c71), menu do usuário em duas colunas no desktop (4f69c1867), link "Prêmios" com badge de contagem (d35ac2c1d), termo de privacidade estático (aba1d579a), chips de provedor redimensionados (7fa5eb78c), escala de input no iOS (5471030ec), fallbacks de i18n de auth movidos pro override (9b0e06450), Top 10 no slug de recommended-banners (27ac69f03) com "ver mais" apontando pra todos os jogos (a7cbfe937), grade sincronizando ao trocar categoria (d2eebe07d), "Pagou" em janela de 24h ocultando dados nulos (fee4febd8) e mensagem VW003 do saque direcionando pra Dados da conta (e8fba8bf8).
  • Seções showcase reconstruídas a partir de categorias reais (11d3a3d3a): "Novidades" passa a sair da categoria filtrável novidades, porque o slug curado jogos-novos não é filtrável no BFF e voltava vazio, e "Mais jogados" passa a usar getByCategory paginado no client. GameGrid ganha maxRows e GameCarousel/GameSection ganham itemWidthClassName, usados só quando a brand opta.
  • Perfis oficiais desativados em 7k, cassino e vera (25eb139cf): featuresConfig.profiles.enabled vai pra false, e o gate único em getActiveProfilesConfig() desliga a superfície inteira sem deletar código — as rotas retornam 404, o widget de sidebar e a row da home se auto-escondem e as entradas saem do sitemap.
  • 7k-bet-br: banner de instalação do app removido das notificações inferiores (503588285) e restaurado no dia seguinte (fa3b2b922); orderBy de slots removido pra usar a ordenação default da API (7d06af9a4); pill de saldo no modal de check-in diário (c29bd9ad2); histórico de login e aposta apontando pra /historico com filtro (7104bd3bd); identificação do usuário logado no chat do Zendesk (d4235ad1d).

Pagamentos

  • client_request_id único por geração de depósito (f9652af1e): o DepositModal gerava a chave de idempotência v3 em handleSubmitDeposit, antes do desvio do Kushki, e só a resetava no fechamento do modal e em "novo QR" — nunca em falha de submit nem em troca de provedor. Depois de erro → trocar provedor → retry, o mesmo client_request_id saía com payload diferente, gerando conflito de idempotência no BFF (o incidente "repetiu o ID"). A chave passa a ser gerada dentro de submitDepositRequest, só quando uma charge é realmente criada, e é resetada em falha, espelhando o padrão do WithdrawModal.
  • Método do Hub encaminhado como veio (24415463c): o front normalizava hub_payment_methods.method pra maiúsculas, o que quebraria valores como credit-card. O Hub é a fonte de verdade, então o valor exato é repassado.
  • Statuses v3 em inglês exibidos, retrocompatível (4ffb68688): WithdrawSuccess aceita AUTHORIZED e AWAITING_APPROVAL ao lado dos valores PT, case-insensitive; TransactionCard ganha badges e chaves de i18n pros statuses novos de falha (failed, integration_failure, provider_rejected) expostos pelo @cactus-agents/accounts. Bumps: payments ^0.21.0, accounts ^2.7.0, i18n ^0.94.0.
  • Depósito Pix em um clique via ?open=pix (63851584c): o contrato ?open=pix&value=<reais> abre o modal pré-preenchido e auto-submete um depósito Pix, levando direto à tela de QR Code. Sobrevive ao round-trip não-autenticado → login/registro → autenticado e cai pra formulário pré-preenchido quando não há método Pix, quando falta registro de documento ou quando o valor é inválido. Gateado por depositPixQueryParam, só na donald-bet-br.
  • Módulos de validação não suportados descartados pra destravar o saque (9761e9dad): o backoffice da donald configurou um módulo contact em validation_modules que o front não sabe renderizar. Num contexto frequent (saque), esse módulo desconhecido é forçado a isValid: false a cada avaliação e, sem UI pra limpá-lo, trava o saque pra sempre. A flag opt-in dropUnsupportedValidationModules filtra os módulos por allowlist antes da avaliação — filtrar a config, e não a decisão já calculada, faz o core recalcular allowed e pendingModule corretamente. Ligada só no override da donald.

Infra / CI/CD

  • Namespace do middleware amarrada ao CACHE_GENERATION (136cba2fa): o getBuildId() produz a chave usada nos caches.open() do próprio worker (assets:*, api:*, ssr:*, ssr-auth:*), e o prefixo era hardcoded da era rasj — bumpar o CACHE_GENERATION não afetava essas quatro namespaces, deixando-as órfãs na geração antiga. Agora lê env.CACHE_GENERATION com fallback pro default canônico, restaurando a garantia de rotação fleet-wide. 008a9a79e alinha o CACHE_NAME do sw.js ao mesmo token.
  • Purge pós-deploy pulado nas brands .bet.br de produção (ops 84eec36): a regulação brasileira exige que as zonas .bet.br apliquem geo-block de edge restringindo tráfego a IPs BR. O step de purge roda de runner do GitHub Actions hospedado nos EUA, então o edge devolve 403 "Region Unavailable" antes da request chegar ao Worker — e o header de bypass de WAF não ajuda, porque o geo-block roda numa camada anterior. Confirmado empiricamente na vera-bet-br. e07455e faz o mesmo na fi-7k-bet, por Cloudflare Access.
  • Dropdowns do Manual Cache Purge auto-sincronizados (ops 827f48b + 97672bd): environment deixa de ser texto livre e vira dropdown com as 32 brands lidas de config/brands/web-base/environments/, e entra o input tag_quickpick com as 30 tags declaradas em config/cache/*.yml. Como YAML de workflow não lê filesystem no render do workflow_dispatch, um script novo regenera as listas automaticamente, fechando o loop de drift. af80834 corrige o bump-cache-generation, que dispatchava deploy.yml no front-web-base quando o workflow real se chama ci-deploy.yml.
  • TTLs do service-api retunados (ops 0df3849): endpoints de 1h caem pra 30min, /v2/bff/features e /v2/bookmaker-settings pra 2min e /v2/casino-game-votes pra 10min; o cap de 1h fica. front-service-api 66de43e reconcilia o fallback hardcoded, que estava defasado em relação ao YAML — o fallback só serve se a injeção de SERVICE_API_CACHE_POLICY_JSON falhar.
  • Duas rodadas de bump de geração (ops e1fbd01 apcsrasj-v3v4, motivo "Categorias Donald", e 4b86ff4 v4v5).
  • fecadm fecha o uplift da semana anterior0ccecf9 passa a rodar build antes do typecheck, porque cli e web importam a saída de build de @fecadm/core, e a7153a4 resolve as falhas de CI restantes e o lint.
  • Avaliação de zero-cache — vários environments foram temporariamente apontados pra main-no-cache (74720bf, 0099bcb, e19a840, 82e594b, eafe257, a38b899) e depois revertidos (2b28f3b, ca9fae3). No mesmo período, 80a92a8 liga auto-deploy da donald-bet-br e f4ca676/d2a843e movem a betpontobet-bet-br pra main-fix e de volta.