Pular para o conteúdo principal
← Voltar ao changelog

Changelog - 12/07/2026

Semana de 06/07 a 12/07, na branch main-7k do front-web-base. É a semana em que o programa de performance/streaming da plataforma sai do papel: 93 commits filtrados, a maioria concentrada num único eixo.

Semana 06–12/07 — streaming SSR e clientLoader em toda a aplicação

  • App-shell streaming SSR, começando pela home (90d1dcfb4): o entry.server passa a transmitir o stream no shell-ready via TransformStream — head processado no primeiro flush (tailwind inline, strip de modulepreload com keep-list derivada do manifest, strip de preloads de imagem não-LCP) e body em passthrough com tail-carry. Antes o stream era bufferizado até o fim pra rodar regex no HTML completo, ou seja, o primeiro byte esperava todo o conteúdo. Junto vem o piloto de deferred data na home: catalog, gameStatsCache, topWins e lastWins saem do caminho crítico e streamam via Suspense/<Await> no documento SSR e via turbo-stream nas navegações .data, com SectionSkeleton no lugar. Continuam awaited homeRows, brand, auth e a page description (meta/SEO/LCP). Cada perna deferida ganhou .catch(() => vazio), então rejeição degrada pra row vazia em vez de 500 na home inteira. streamTimeout de 10s com abort combinado.
  • Estendido pra game page, hubs e navegação (85a108c2d): o loader da página de jogo cai de 3 gates sequenciais pra 2 awaits (game e brand, o mínimo pro meta/SEO), deferindo stats, winners, related, providers, voteState e as seções showcase. O getStatsRaw, um endpoint do BFF não cacheado que só alimentava o DevPanel, foi movido pro gate de dev — produção deixa de pagar por ele. Nos hubs de cassino e ao-vivo, gameStatsCache e os wins passam a deferidos no caminho de rows. E em _layout.shouldRevalidate, troca de pathname deixa de revalidar o layout, o que remove a chamada de auth por usuário — transatlântica — do caminho crítico de toda navegação SPA logada; login e logout seguem revalidando via revalidate() explícito. Skeletons CLS-zero novos: WinsCombinedSkeleton (o fallback anterior causava ~137px de shift no desktop), WinsMarqueeRowSkeleton, PaidTodaySkeleton, ListWinsSkeleton e GameRowSkeleton derivado do gameCardConfig, com widths corretos por breakpoint e por brand.
  • clientLoader deixa a navegação game → game instantânea (1812d8b07): a navegação SPA pra /cassino/jogar/:provider/:game esperava o .data inicial, que aguarda o getDetail (round-trip de 0,5-1,5s em cache miss), travando a troca de rota inteira. O clientLoader devolve o loaderData na hora — serverLoader() disparado sem await, cada campo encadeado como promise — então a URL troca instantaneamente e o conteúdo preenche por streaming. game, frontDescription, seo e faqEntry foram combinados num único boundary de AwaitValue cujo fallback reserva a geometria exata do conteúdo real, e slug/showcase ficam síncronos porque são deriváveis de params/featuresConfig sem round-trip. 5c24a1354 acrescenta o skeleton pixel-perfect e o seed otimista do card clicado.
  • Padrão propagado pro resto das rotasb3e8c151d (hubs cassino e ao-vivo), bb0b2c012 (categorias e provedores), f241a9145 (home, incluindo seed otimista a partir do homeRows já cacheado na store e o bundle de favoritos/recentes virando um único favoritesBundle streamado), 761088aa6 (VIP e gamificação) e e6fe562f1 (conteúdo WP, legal e busca, com legalTerms deferido). cc237adfc centraliza o preview de dev-skeleton num módulo único e removível; 49ef0d808 e d8401122b corrigem imports .server que vazaram pro caminho client.
  • Dois defeitos reais achados e corrigidos no próprio programa. 885f0e92c: o encoder de single-fetch do React Router corre toda promise deferida contra o streamTimeout, e perna pendente virava frame E:["Unexpected Server Error"] dentro de um body 200, por fora do .catch do loader; pior, errorElement={null} é falsy, então o <Await> re-lançava pro ErrorBoundary da rota em vez de suprimir (a tela renderizava e depois virava erro); e o worker cacheava o .data 200 com o frame de erro embutido por 60s, servindo o mesmo erro em HITs subsequentes. Fix: errorElement truthy (fragment vazio) em AwaitValue/AwaitWins e na home. afc20a73d, resultado de revisão adversarial: o buildSpaShell bufferizava o stream inteiro pro usuário logado (TTFB = render completo, ~11s no pior caso) e devolvia um shell morto; Set-Cookie de tracking bloqueava o cache compartilhado pra todo visitante anônimo frio; o cache autenticado não tinha guard contra payload envenenado ou documento truncado; e a home resuspendia pra skeleton genérico em todo login/logout. O buildSpaShell foi removido e o autenticado em rota pública passa a receber a mesma stream real do anônimo.
  • Navegação e stats estáveis na página de jogo (dfad863b1): getStats e getVoteCount ganham timeout fail-fast de 1200ms, com a promise original registrada no waitUntil — assim o warming completa em background mesmo em cold-miss, o que o timeout sozinho mataria. As duas ondas sequenciais de Promise.all do loader foram fundidas, removendo um round-trip serial.

Zero recursos render-blocking

  • Tailwind inline no HTML em produção (7cc1f51e2): o <link> do Tailwind (32 KB gzip, 88% não usado na home por ser SPA multi-rota) era o último recurso render-blocking do site. Em produção o CSS inteiro vai inline no fim do <head> via processHtml — cerca de +25 KB brotli num HTML que já é edge-cached e que a navegação SPA nunca repaga. Medido no preview: HTML gzip de 210 pra 199 KB já incluindo o CSS. Em dev o root.tsx mantém o <link> pra preservar o hot-swap de CSS do Vite.
  • Fontes de brand inline (3718b9bb6): o root linkava ~/styles/brand-fonts.css?url pra toda brand, mas o arquivo do base é vazio de propósito — resultado: um request render-blocking de 1 byte em todas as brands, que o PSI flagava como bloqueio de renderização num CSS vazio. Agora o CSS é importado com ?inline e vai num <style> só quando tem conteúdo. Brands sem fonte própria não pagam nada; a donald-bet-br inlina ~1 KB de @font-face e também deixa de pagar o request.
  • Hidratação direta e modulepreload de boot-critical (82c94bfd1): o boot-loader via Blob (gate loadrequestIdleCallback de 5s) estava morto em produção — o regex exigia começar com import * as, mas o React Router 7 emite o import do manifest primeiro, então nunca casava. Removido com nota histórica. O gate real era o requestIdleCallback de 2s do entry.client, e agora hidrata direto. O strip de modulepreload passa a preservar os ~5 chunks que o script inline de hidratação importa, então a rede corre durante o parse em vez de só depois de ~880 KB, e preloads de imagem com media= sobrevivem ao strip.
  • Banner de cookies SSR-first com guard pré-paint (a2bd19eae): o <p> do banner era o elemento LCP da home mobile no lab (render delay de 4,7s no PSI). Sai o lazy() + client-only + setTimeout(2000); entra render no HTML SSR com script inline síncrono no <head> que seta data-consent-given no <html> antes do primeiro paint — CSS esconde pra quem já consentiu, sem flash, sem tocar o cache do edge e sem hydration mismatch.
  • Decorações como SVG inline e ligas de cor fixa como <img> lazye24a5b172: o decoration.svg do corner-diagonal era o elemento LCP de campo dominante da home 7k (~300 mil pageviews a ~5,7s no RUM) e o emblem.svg dos CTAs também aparecia como LCP; <svg> inline não é candidato a LCP por spec, então os dois viraram um kind novo inline-svg com asset por brand importado em build-time (bônus: −2 requests por página). 8c839f6b2 ataca o outro lado: os SVGs inline eram 44% do gzip do HTML da home (232 svgs, 311 KB raw, 46% dos nós do DOM). As 7 ilustrações de liga de cor fixa (~90 KB inline, −151 nós) saem do HTML SSR e viram <img width=52 height=52 loading=lazy> de assets estáticos otimizados por svgo (91 → 43 KB); as 22 ilustrações temáticas, que usam currentColor, seguem inline. Campo iconSrc aditivo e opcional, então outras brands ficam intactas.

INP, CLS, TBT e payload

  • INP (27562d6a8): o CTA "Jogar" era o elemento INP dominante do RUM (~1,4s) — passa a pintar o feedback barato, ceder o main thread com double-rAF e só então montar o overlay fullscreen, e o backdrop-blur saiu da top bar. Chunks de auth ganham prefetch no idle, matando o "tap morto" no primeiro toque em Entrar/Cadastrar. No registro, started_register é deferido pra fora do task do keystroke (o #register-email tinha INP de ~600ms no RUM).
  • CLS (96e1f1b9b): no sheet de auth (CLS 0,345 no RUM), o avoidKeyboard foi desligado — o padding do teclado virtual re-layoutava o form a cada focus/blur — e o Turnstile ganhou espaço reservado (300×65) antes do mount assíncrono. body e shells trocam min-h-dvh por svh, porque o collapse da URL bar re-layoutava a página inteira (CLS 1,018 no body da CL). Em /esportes, o spinner passa a ter a mesma geometria do iframe final. Em /usuario/*, skeleton com geometria real substitui o spinner (CLS 0,124).
  • TBT (71857c25e): os 4 publishers de CSS vars (--header-h, --topbar-visible-h, --mobile-nav-content-h e o offset do consent) entrelaçavam leitura de geometria com escrita de estilo no flush pós-hidratação — cada escrita invalida layout global, porque as vars alimentam calc() em toda a árvore, e a leitura seguinte força reflow full-document (~205ms somados no PSI). O layout-vars-scheduler novo agenda tudo num rAF: todas as leituras primeiro, depois todas as escritas, cerca de 1 reflow.
  • Imagens sem dimensão e animação não-composited (619f0ba57): 15 selos do footer sem width/height (auditoria "unsized images" do PSI) ganham dimensões reais e decoding=async; o autorizado.png da 7k cai de 500×142/7,2 KB pra 400×114 paletado/3,2 KB; e o pulseRed, que animava box-shadow em loop acima da dobra (paint na main thread a cada frame), vira um ping composited com transform+opacity e motion-reduce. 77d3032ee completa com dimensões no logo e nos sponsors do footer e variantes sized nos ícones de CDN; b5569c007 corrige quality e variante mobile do preload do banner.
  • Payload da home cortado 43% (5a02521b4): o loader do _layout embarcava os 5 documentos legais completos (~170 KB raw, só o T&C são 94 KB de HTML) em toda página, mas o footer só usa route e title — passa a ir a versão slim, com o conteúdo integral ficando na rota /pagina/:slug. O brand.settings.terms (~39 KB, zero consumidores no app) é zerado com clone raso, porque o objeto de brand é cacheado server-side e não pode ser mutado. topWins/lastWins vêm com 100 do BFF e são truncados em 30. Turbo-stream da home medido: 345,6 KB → 195,3 KB.
  • Modal base fora do grafo eager (5d6534ef6): CheckinTrigger e FtdOfferProvider importavam o Modal base estaticamente (react-modal-sheetframer-motion, chunk de 156 KB), então ele entrava no grafo eager de _layout/_index em toda página — o "Modal 71% unused" do PSI. Virou React.lazy gateado na primeira abertura, com prefetch no pointerenter/focus. 3cbe3c674 fecha a última cadeia eager pro Modal base, gateando o MatchXRankingModal, e ainda para de buscar as feature flags de check-in pra visitante anônimo (2 dos 3 requests de flag no PSI eram RTT puro desperdiçado). 2194c5294 adiciona prefetch no idle do DepositModal (29,7 KB gz) e do ValidationStepsModal (20,5 KB gz), os dois hot paths que pagavam RTT + download + parse no clique.
  • SDK da rogue fora do caminho crítico de toda página (0ab3c67c0): o matcher includes("@cactus-agents/sports") casava por substring com @cactus-agents/sports-rogue (SDK completo, dist de 844 KB com side-effects top-level) e fundia tudo em vendor-cts-sports, que o layout importa eager só pra usar 12 KB de helpers. Resultado: 770 KB / 213 KB gz avaliados em toda página — inclusive em brands que nem usam a rogue. O sports-rogue sai do grupo nomeado e o rolldown code-splitta pelo grafo natural; como é lazy-only, cai num chunk async. Verificado no build: SDK de 558 KB fora dos imports do _layout, React no vendor-framework, zero betslip em chunk eager.
  • Acessibilidade (78807398d): o PSI deu 82 em acessibilidade no fluxo de registro, e o commit fecha os apontamentos — aria-label dinâmico nos 6 toggles de mostrar/ocultar senha com hit area de 32px, aria-label no backdrop do react-modal-sheet (a lib renderiza <button> quando há onTap) e dois ajustes de contraste token-safe (3,75:1 → 6,9:1 e ~2,6:1 → 5,0:1).

Cache, worker e TTFB

  • Documento e .data na mesma janela de frescor (2fac84d4d): três relógios divergentes serviam snapshots de idades diferentes pro mesmo usuário entre o primeiro paint (documento) e a volta por navegação SPA (.data) — a causa-raiz do ranking "Pagou muito" trocando valores. Quatro correções: a resposta .data saía sem Cache-Control (só a cópia interna levava o header, porque o handler de single-fetch não passa pelo entry.server), então a Cloudflare nunca cacheava .data enquanto cacheava o doc; DEFAULT_TTLS de .data era 30/30 contra 60/120 do doc, agora alinhados; resolveSSRCacheTTLs recalculava dataSWR = dataSMaxAge, sem o ×2, o que dava 60/60 efetivo mesmo com o default corrigido — a regra passa a ser SWR = 2 × s-maxage; e KV_SSR_TTL cai de 3600 pra 180, porque o fallback de KV repopula o Cache API sem re-renderizar, então um POP frio podia servir um snapshot de até 1 hora. 6eb02f314 fecha o resíduo com um session-pin: getSessionPinnedGames congela a primeira lista não-vazia da sessão SPA, porque o totalPaid é janela móvel de 24h e oscila nos dois sentidos entre fotos — dentro da sessão o ranking fica estável, e full reload pega foto nova. 1dd77e927 é o hotfix de um 500 causado por esse session-pin ter nascido .client (undefined no SSR).
  • Camada api: removida do worker do front (3af6997b5): o cache de resposta crua do BFF pra /api/* (API_CACHE_RULES, getApiCacheRule, handleApiCache, kvApiKey) foi removido por completo, sem código morto. Motivo: todo /api/* proxia pro BFF via service binding e o front-service-api já é a única fonte de cache raw-BFF, com TTLs em front-ops/config/cache/service-api-defaults.yml. Cachear no front era redundante e causava staleness por conflito de TTL — a camada fixava 3600s enquanto o service-api usa TTLs menores e intencionais por endpoint. O único path que não passava pelo binding, /api/cactus-sportbook/*, agora também vai por ele. O worker do front deixa de emitir X-Cache em /api/*.
  • Cache de shell morto removido — gravava dado de usuário em cache compartilhado (22ceda50c): o buildSpaShell tinha um cache cujas leituras estavam desligadas desde 05/05, mas cujas escritas seguiam vivas: cada request de usuário logado gravava ~335 KB com o loaderData dele (auth, token, smarticoHash, favoritos) numa chave sem dimensão de usuário, compartilhada entre todos os logados, sem ninguém ler. Era munição carregada porque o sanitizer que deveria zerar auth é no-op no React Router 7.14 — o loaderData não vive no objeto que o regex casa, e sim no turbo-stream, que passa intacto. Religar as leituras serviria o perfil de um usuário a outro. Como as leituras já estavam off, remover não muda comportamento: só elimina o dado at-rest e um caches.match + put + kvPut de ~335 KB por request logado.
  • Warmer de cache via cron (5af3d7649): kill-switch CACHE_WARMER_ENABLED (default off, sobe inerte). Quando ligado, o handler scheduled() re-renderiza o SSR anônimo dos paths quentes em modo force-fresh e regrava o snapshot KV global, rodando in-process (chama withSSRCache e o request handler direto, sem fetch externo) — então não passa por Cloudflare Access nem pelo geo-block regulatório. Cron a cada 2 minutos, 12 testes novos. Ops f821bba move a config do warmer do dashboard da Cloudflare pro front-ops (cache_warmer.{enabled,paths,origin} injetados no deploy como o BUILD_ID e o CACHE_GENERATION), ligado na stage do 7k e comentado em produção; e8a3761 ajusta paths. f4ab0f017 corrige uma deriva de semântica introduzida pelo merge do streaming: o label X-Cache: WARM-STORED vinha do nível de header, então um warm-render truncado ainda era 200 e era contado como aquecido mesmo quando o guard de completude dropava o body — agora bufferAndCacheIfSafe devolve boolean e o label reflete se gravou de fato.
  • Fase 1 e fase 2 do doc público compartilhado pro logado590253fec (fase 1) torna o auth 100% reidratável no client: /api/auth/profile passa a devolver smarticoHash, entra uma store app/store/smartico.ts (zustand puro, SSR-safe) e o SmarticoInitializer usa prop ?? store. Na mesma leva, fetchProfileFromApi vira resultado discriminado (ok/unauthorized/error) — antes colapsava 401/440 e falha de rede/502 no mesmo null, o que deslogaria o usuário num soluço de rede — e, no caso unauthorized, além de limpar is_authenticated passa a apagar o jwt_token HttpOnly, porque antes o isCookieAuth do worker seguia classificando como logado e toda navegação re-disparava chamadas 401 em loop. 24ecb4ad7 (fase 2) adiciona o kill-switch SHARED_PUBLIC_DOC_FOR_AUTH, default off: quando ligado, um GET de usuário logado em rota pública cacheável tem os cookies de auth removidos no entry do worker, caindo no bucket compartilhado e renderizando neutro. O strip reusa o conjunto canônico e completo de cookies de ~/utils/cookie.server — como o ApiClient reencaminha o header Cookie inteiro pro BFF, remover só um subconjunto vazaria dado per-user pro cache compartilhado (postmortem de vazamento de saldo de 29/04). 31 testes novos.
  • Server-Timing no caminho do primeiro byte (1dbe2233c): novo util withServerTiming acumula spans no loadContext e o entry.server emite o header, visível no DevTools e legível por RUM (spans de brand/auth/legal no _layout e home/gbase/topwins/lastwins no _index). Junto, o _layout deixa de esperar brandauthlegalTerms em sequência: com o BFF em eu-central-1 e o worker na edge, cada perna sequencial custava até um RTT transatlântico, e as três são independentes, então viraram Promise.all. Amostra de cache frio: auth 3ms, legal 58ms, brand 62ms contra 1,1-2,3s dos catálogos de jogos — o header já aponta onde o TTFB de cache-miss realmente mora. 5ee474a0e propaga o header pro shell de usuário logado, que antes o descartava.
  • Scripts de marketing na estratégia engagement (4abdac92c): lane opt-in por brand (marketingScriptsStrategy: 'engagement') que move o download dos scripts conversion-critical (gtm.js, fbevents, Taboola, Kwai, Webtrends, Floodlight) pra primeira interação do visitante ou um deadline configurável (default 5s), o que vier primeiro — sem trigger de idle, porque requestIdleCallback dispara quase imediato em página ociosa. O que separa isto do gating de junho, que foi removido por quebrar o marketing, é que as filas nascem síncronas no mount: primeGTMQueue (com dataLayer e Consent Mode v2 default à frente da fila) e primeFBPixelStub (stub do fbq + fbq('init'), sem o qual trackFBEvent no-opa); Kwai e Taboola já são queue-by-design. Todo evento disparado antes do download é enfileirado e processado em ordem, com zero descarte — a perda residual é o visitante que sai antes do deadline sem interagir. Sem branch de bot: o CrUX mede a mesma página que o Lighthouse. Racional: os scripts do container somam ~700ms de long tasks na janela de load. 7k opta no teste com 5000ms; o default global segue eager.

Segurança

  • API_BASE_URL nunca mais chega ao client (c7dd1513f): o host final do BFF estava exposto ao browser e usado em requests cross-origin diretas. A auditoria confirmou três vetores alcançáveis pelo client — API_BASE_URL fazia parte do ClientEnv e portanto era serializado no loaderData/HTML; user.client e SocialAccountsSection batiam cross-origin no BFF do próprio browser; e register/validate-phone devolvia a URL absoluta no body da response (debug:true, com campos url/requestInfo/raw), que foi o vazamento observado em produção. Correções: API_BASE_URL removido do ClientEnv com aviso pra nunca reintroduzir e os loaders deixando de serializá-lo; rotas same-origin novas GET /api/user/social-accounts e DELETE /api/user/social-accounts/:id; SocialAccountsSection migrado pra fetch same-origin; envelope de validate-phone reduzido a { ok, message }; e o interceptor de 401 restrito a same-origin, com configureApiFetchInterceptor e trustedOrigins removidos.
  • Login social gateado enquanto desativado (223e7f183): o loader de /api/auth/social/:provider fazia redirect(302) pro BFF, e o header Location expunha o host final ao browser. disableSocialAuth é true em todas as brands, então o caminho é inalcançável pela UI hoje, mas o vazamento era latente — loader e action passam a devolver 404 (social_auth_disabled). Registrado no commit que, antes de religar social, o start do OAuth precisa ser brokerado same-origin.
  • Complemento no core (b343bca): o ApiClient fazia console.log incondicional de método, URL e payload em send() — no client isso imprimia a URL absoluta do BFF e o payload, que em auth e registro inclui e-mail, senha, telefone e documento. Os logs foram removidos (server-side use requestInfo, já gated por config.debug e com headers redigidos). No mesmo commit, proxyRaw() deixa de fazer fetch() global direto no BFF e passa pelo service binding API_SERVICE, como o send() — antes o tráfego de proxy cru escapava do cache raw-BFF do front-service-api.

Ondas 1 a 3 do refino de UX do 7k

  • Onda 1 — limpeza de UX (5a033ffdf): primeira onda do backlog de refino da 7k, com todas as mudanças gateadas por flags novas ligadas só no override da 7k, então as outras brands ficam byte-idênticas. Flags: showGameCardStats (7k false — oculta a pill "online agora" e o balão de estatística nos cards comuns, preservando o valor nos cards com payoutAmount), clientStatsReorder (7k false — desliga o reordenamento client-side que causava "cards pulando"), gameCtaGuestPlaysOpensLogin, gameVotesTotalOnly, hideUserSummaryActions e showHeaderPromotionsGift.
  • Onda 2 — página de jogo no mobile (b6fbf47db): diferente da onda 1, aqui são correções de responsividade universais, desenhadas pra zero regressão em larguras comuns. O breadcrumb do item atual truncava com elipse no mobile, ficando ilegível — agora a trilha rola na horizontal com o scroll fixado no fim (item atual visível primeiro), e truncate virou whitespace-nowrap. O componente é compartilhado por cerca de 40 rotas.
  • Onda 3 — atividade recente na carteira (5364ec3a8): novo WalletRecentActivity reusando useTransactions e TransactionCard pra mostrar os 10 lançamentos mais recentes direto na página da carteira, com link pro extrato completo, gateado por showWalletRecentActivity (só 7k). Quando o bloco aparece, o card "procura o extrato?" é ocultado pra não duplicar o link. Suportado pelo core 9308848, que publica wallet.pending_notice_title e pending_notice_desc nos 7 locales.

Core / SDK

  • @cactus-agents/sweepstakes nasce (cf89ca9): camada de core dual-currency, loja e resgate pra o produto 7wins — aditiva, gateada e default-OFF em todos os packages, sem mudança de comportamento pras brands existentes. O package novo (0.2.0) cobre balances, wallet-preference, currency-switch, elegibilidade, resgates e pacotes/compra na loja; accounts ganha tipos de coins, slice reativo de activeCoinId/coinBalances, formatCoin sem símbolo e useFormatMoney coin-aware; games passa a levar StartGameParams.currency pro start-game-v2; country-config adiciona os EUA (USD, en-US, sem CPF); e o i18n em inglês ganha as strings de coins, resgate e compra. Seguem d15a7af (escala de valores de coin pra unidades de wire), 182b9f9 e 0a4b4fb. É a primeira aparição do 7wins em qualquer registro do changelog.
  • @cactus-agents/payments (ab76ab8): buildDepositPayload passa a emitir card_token junto com kushki_token, com o mesmo valor, porque o backend está migrando a integração Kushki (CHL) pro contrato de one-step payments — o legado segue lendo kushki_token, o novo lê card_token. Aditivo, sem mudança de tipos.
  • @cactus-agents/i18n (d2acf99): chaves de aria-label do toggle de senha (register.password_show/hide), consumidas pelo pass de acessibilidade do base.

Brand Overrides

  • betpontobet-bet-br ganha bottom nav estilo Betano (4515f0cba): variante mobile-nav-betano vertical-aware (conjunto CASSINO fora de /esportes, conjunto ESPORTES dentro), com NavTile/dispatcher/resolve estendidos pra rotatingLabels, liveDot, vertical-toggle e open-sports-menu. O botão "Menu" da barra de esportes passa a alternar o menu do SDK via instance.toggleSportsMenu(); a barra fica sem "Ao Vivo" e ganha um item "Bônus". Consome sports-rogue ^0.25.0. Iterações de FAB e polish em 274dba966, 207e018f9, 570caca87, abc2ac887, ba9c19477 e 7c0185a09. 5ff3471b2 desativa o GTM em produção na brand e alinha a home aos ajustes de 7k e vera.
  • 7k-bet-br: GTM reativado num container novo (f0f37b214), fechando o desligamento da semana anterior. 315f989c7 migra ~157 assets que a 7k carregava do CDN de outra marca pro próprio CDN da brand (álbum e sticker-catalog, campaign widget, sidebar e escudos de posição de torneio) — os mesmos image IDs resolvem em qualquer hostname do CF Images da plataforma, e as 157 URLs foram verificadas com 200; o RankBadge tinha os escudos hardcoded no componente e virou brand-overrideable por um campo opcional em gamification-ui.ts, com o default preservado pra não exigir propagação. Também: selo Google Play restaurado no footer (f9b3afe0b) e virando link direto pra loja quando a brand tem a URL (0473b0810), logo do header fechando o jogo em tela cheia (cc6f71acf), botão "Baixar App" da sidebar levando pra LP interna (4d2160bb6) e banners da campanha 3M desligados (e1de6107d).
  • Widgets flutuantes com comportamento nativo (655f3332b): entrada com atraso, auto-recolhimento numa peek tab colada na lateral e swipe pra dispensar (hook novo useFloatingCard), com orquestração unificada do stack (cards e tabs recolhidos dividem uma coluna sem sobreposição, back-to-top empilha acima). Config nova floatingWidgets com override por brand: 7k e variações desativam tudo, betpontobet fecha de vez, donald e pb.bet recolhem sozinhos, demais em manual.
  • --mobile-nav-h com env(safe-area) vivo (e19446b89): a var era um snapshot em px que congelava o env(safe-area-inset-bottom), que muda em runtime quando a URL bar recolhe ou expande no scroll, e o ResizeObserver (content-box) nunca re-dispara quando só o padding muda. Consequências reproduzíveis só em aparelho real: gap de ~17px entre a sidebar aberta e o nav, e widgets flutuantes grudando ou afundando no nav ao rolar. O BottomNavShell passa a publicar só a altura de conteúdo (--mobile-nav-content-h) e --mobile-nav-h vira calc(content + env()*0.5) no CSS, resolvido pelo browser ao vivo.
  • Referral "Indique e Ganhe" v2 portado (a4be35e01): variante indicator-v2 com fluxo de resgate por indicação, proxies /api/user/referral-indicator-v2 e /api/user/referral-claim, botão "Resgatar" gateado por 100% de progresso e kill-switch de feature flag. Exclusivo da vera-bet-br no momento do port. 8dd1182ec e 0fc9c4c2b adicionam ligas e ícones de esporte na mesma brand; 6cd95da74 ajusta as rows da home e do cassino ao vivo.

SEO

  • Categoria canônica todos-os-jogos com 301 do alias (ededcc06b): sidebar e header de todas as brands BR apontavam pra /cassino/categoria/all, alias legado que renderiza o mesmo conteúdo do slug canônico, dividindo sinal de SEO, CrUX e PSI em duas URLs. Os configs foram trocados pro canônico em 9 pontos de 8 brands e o loader da categoria passa a responder 301 pro canônico quando recebe qualquer alias, preservando a query string.
  • Texto long-form da home 7k reescrito (fd87c47f1): substitui o front_description portado do Nuxt legado, que fazia keyword stuffing e citava um fato desatualizado sobre licenciamento pré-regulamentação, por conteúdo no modelo das casas regulamentadas — heading e disclaimer de jogo responsável sempre visíveis (o PageDescription colapsa em ~2 linhas), seções H2/H3 e FAQ de 8 perguntas em <details>/<summary>, indexável porque o conteúdo está no HTML e abre/fecha sem JS. Dados de operadora, CNPJ e portaria espelham o texto legal do footer.

Infra / CI/CD

  • Auditoria de paridade skeleton × resolvido nos testes E2E (b39f9a4): spec declarativo (ROUTE_CASES) que congela o clientLoader via ?__skeleton=1, mede getBoundingClientRect dos blocos-chave e falha se o delta passar de 4px — 6 rotas cobertas (home, hub, ao-vivo, categoria, provedor, jogo), estável em 12 execuções. É o guard anti-drift do trabalho de streaming, e o único trabalho substantivo de E2E do período. 78d82d0 adiciona as rotas de VIP/gamificação e b5d38b9 as de conteúdo e legal.
  • Environments novos — ops dd5ed7e cria stage-spa e stage-cache do 7k, espelhando a stage existente, pras duas linhas de experimento (app-shell SPA e cache); 952f488 cria a stage da brand de sweepstakes 7wins; base 393c6ebf3, bc4543ba7, 7d679da57 e 42ee10b60 sincronizam os dropdowns de dispatch. 5f4e09c bumpa a geração apcsrasj-v13v14, e ops 9ce77f7, 95285f9, 8552ee1, 66b3c8f, 34247cd e 7e995ba movem default_branch de cl-bet7k-com e de stages entre main-b, main-cl e stage-workers-cache, com 557ceda apontando a stage do 7k pra main-7k.
  • d5e538480 adiciona triggers temporários de URL pra teste de performance (ferramenta de medição, não comportamento de produção); 7ee2df289 reabre o modal de depósito em navegação repetida com ?deposit=.

Documentação

  • 6cc78cfee documenta em código o dual-write de kushki_token e card_token no fluxo Kushki, acompanhando o core ab76ab8. front-service-api 418708a corrige um comentário que confundia pin de região com Smart Placement.