Documento SSR auth-agnóstico — useAuthGate e o expurgo de dado de usuário dos loaders
Janela desta entrada — cobre 20/07 a 26/07 (171 commits em main-7k, a semana mais movimentada do trimestre). Foi também a semana em que main-fix e main-7k foram cruzadas nos dois sentidos (PR #1770 e PR #1773), então muitos assuntos aparecem duas vezes no histórico com SHAs diferentes — cada item abaixo cita um SHA só.
O documento SSR deixa de saber quem é o usuário (PR #1808, branch feat/user-data-out-of-ssr) — 6b1f32a90 remove auth e smarticoHash do loader de routes/_layout, e cb1b41c26 introduz o gate canônico app/hooks/useAuthGate.ts, client-side e sem snapshot SSR: o SSR sempre pinta convidado, o cookie is_authenticated serve como sinal otimista pós-mount e a store reativa assume depois da hidratação. Regra que nasce daqui: visibilidade e comportamento de UI nunca devem ser decididos com useRouteLoaderData("routes/_layout"), porque aquele é um snapshot SSR que não atualiza quando o login/logout acontece por modal.
Loaders per-user desmontados um por um — d006a45fc (wallet, fetch 100% client-side), 62fd7b064 (rewards e income-report), 7de40d656 (página de favoritos), f17975eff (favoritos e recentes da home), 5683cdb3c (a página de jogo passa a serializar só a contagem pública de voteState; o userVote vira client-side), e23934b23 (bootstrap do álbum via /api/album/bootstrap). 28366ffca é a reescrita larga do caminho de dado de usuário no SSR.
Guardrail em teste, não em revisão — 282070eca cria o leak-canary: loaders públicos não podem serializar dado de usuário, e o teste falha se voltarem a fazê-lo. e06e384a0 fecha um furo do próprio guardrail (o canary passou a resolver todos os campos-promise do _layout, não só os síncronos). f8d6acf53 testa o useAuthGate contra a store real e f4d4eb059 deduplica React e Zustand no vitest.config — a resolução source-direct estava trazendo uma segunda cópia de React pela via de alias.
Reconciliação do Smartico — 7c5659fba mantém o smarticoHash correto em login e logout dentro da sessão SPA agora que o doc é auth-agnóstico, e 338c824b0 adiciona guard de corrida no fetch dele (além de corrigir comentários que descreviam o gate SSR antigo).
~55 commits feat: de assunto curto, na branch stage-spa-v2 (PR #1803 e PR #1810) — o histórico desta parte é deliberadamente pouco descritivo, então vale registrar por RESULTADO em vez de por commit. O que a branch entregou: HTML cacheado no service worker com speculation rules (43a35e2f8), max-age com 304 (bb2347090), janelas de cache ampliadas pra 30 dias com regras próprias pro .data (b86884227), cache no Worker (e88355824), md5 de conteúdo (8a187c95f) e cache tag (d867ef197).
Cache novo no localStorage, com TTL por chave — 57ba59e73, 7fa0c292f e b9ec339f3 (este último desliga o cache de jogo no localStorage). 5e70e157b adiciona um uuid de cliente e b6c9cbfe5 faz o app pré-buscar a página que acabou de carregar, porque a navegação SPA só pede .data e nunca cacheia o documento original — sem isso, um F5 sempre batia na origem na primeira vez.
Painel de cache e reload cooperativo — 2a901cb5f e ac28ff927 criam o painel de build e caches, 33b331032 o modal de limpar cache, e c23af240d mais b1a1bdeeb introduzem a verificação de "usuário ocupado" (inclusive modal de cadastro aberto) antes de forçar reload por versão nova. 3dc1a14cb e 3611fde7d cuidam do sw.js em si (param novo por build, remoção do cache).
Marketing e tracking na casca — f8b30c348 e 4bf5d88df movem o redirect e o cookie de marketing pra /mkt/; b1939c0ce remove parâmetros de tracking da URL cacheada. 4250c4793, f079cdfe9 e 70c23a51a endereçam vazamento de dado entre variantes de cache e o par user_id/smarticoHash — assunto consolidado no trabalho de doc auth-agnóstico acima.
A parte disciplinada do mesmo tema (PR #1756) — 6f3bc4048 implementa stale-while-revalidate no service worker RESPEITANDO o Cache-Control da borda: lane dinâmica pra navegações, .data e GET /api/* anônimos same-origin, com a janela calculada por resposta (max-age, senão s-maxage, senão 1 h, mais o stale-while-revalidate). Só respostas com public explícito entram — private/no-store/no-cache, /user* e os probes /api/version e /api/clear-cache nunca são gravados nem servidos. O commit também conserta um guard de logado que era código morto (Cookie é forbidden header em service worker) usando a Cookie Store API no cookie is_authenticated. Namespace dyn-* separado, build-gated, limite de 150 entradas, lane desativada em dev.
Correções de rota do experimento — 919ee1b6d (PR #1811) evicta a navegação stale quando a revalidação volta 200 non-HTML; 1d7c2c1cf (PR #1809) faz a higiene pré-deploy (defer nomeado do version-check e content-visibility iOS-safe); 5f0ba65ed e ee210b234 (PR #1816) consertam os legacy redirects na casca — o redirect passa a ser resolvido no clientLoader e o de-auth do Worker preserva o redirect manual em vez de seguir o 301 legado por dentro. f5fa1427d e 14ea582a1 trocam TournamentsSection e MainLeagues por imports estáticos pra matar o flash de título e de skeleton no F5.
Identidade Chile ligada ponta a ponta (ainda com mock DIDIT)
Camada de dados — 75e506097 cria as rotas de proxy pre-auth espelhando validate-document.ts: api/auth/documents.validate (RUT contra o BFF /documents/validate, tratando Ramo B em HTTP 200, CL00404, BDT00008, fallback de 404 e já-cadastrado) e api/auth/documents.confirm-birthdate (data contra /documents/confirm-birthdate, tratando sucesso, Ramo B, IDENTITY_BIRTHDATE_MISMATCH, 429 e 404), mais services/identity.types.ts e identity.client.ts.
Mock gated em dev — 45d63534c cria o mock de validação DIDIT e 8f862f8ad e 196dd2b74 o restringem a build de dev nos dois proxies, pra o fluxo poder ser desenvolvido antes do BFF real existir.
Campos ligados — bc57748ea conecta o RutValidationField ao proxy /documents/validate e 93bb02c32 conecta o BirthDateValidationField ao confirm-birthdate, com o wiring no RegisterModal. 2d9a0348b mapeia o 422 sobrecarregado do BFF nos três casos reais (mismatch, blacklist, formato).
Especificações escritas junto com o código — 4f3709ec3 registra que RUT não tem tamanho fixo (a validação é estrutural, de módulo 11, não de existência), e61beba67 confirma o contrato DIDIT (formatos de payload e códigos) e 857634714 esclarece Ramo B versus timeout (a data valida contra cache, sem timeout no passo 2).
Torneios da família 7k redesenhados — cinco rodadas em uma semana
Estado final: card em variante panel e detalhe em layout tabbed. be5739387 (PR #1737) abre com a variante feature mais detalhe overlay, e 8ddb82530 faz o retrabalho completo depois do feedback de produto ("nada a ver" com a referência), com pesquisa no legado Vue e no repo oficial smarticoai/public-api antes de codar. a7c6e67bb (v3), 8fdd42d3d (v4, punch-list do produto) e 661b53e96 (v5) fecham o ciclo. Cinco rodadas de redesign da mesma tela em sete dias.
O que o card panel mostra — painel bg-secondary com a arte do torneio dissolvendo no topo por gradiente (não scrim preto), título, prêmio e jogadores sobre o fade, pill "termina em", CountdownGrid de 4 boxes e mini-leaderboard top-3. O top-3 vem de useTournamentTopPlayers, que só busca o detalhe quando o card entra no viewport, com cache por sessão e dedupe in-flight; sem dado, a seção simplesmente desaparece.
O que o detalhe tabbed mostra — hero overlay, tabs Classificação/Jogos/Regras com countdown na linha no desktop e em pill acima no mobile, 4 info cards com datas reais formatadas pelo intlLocale, pódio top-3 e tabela paginada de 10 do 4º em diante. Aposta e rodadas não têm fonte na API da Smartico e por isso aparecem como traço com asterisco de aviso, seguindo a convenção do projeto de nunca calcular no front o que a API não manda. Dados por jogador (apostas, vitórias, percentual, sparkline) foram omitidos de propósito pelo mesmo motivo, confirmado em dupla checagem no repo oficial.
Ajustes finos e a rodada "menos verde" — 14b6f1837 entrega skeletons de shape exato pros layouts novos (zero shift), 76dbdd4d9 e 79f5acc3e acertam o countdown no desktop estreito, a86767fc6, 6f8a4da04 e b3ce5eddb neutralizam a faixa "termina em" e as bordas das tabs. e020cfa1b (PR #1755) faz o rework visual "menos verde" em missões, loja e páginas VIP — o verde deixa de ser moldura e vira acento (prêmio e CTA), com o token accent adicionado aos colors.ts das 4 brands da família porque override de brand é substituição de arquivo, não merge.
Loop de remount do widget (PR #1731) — 8629ed700 corrige a causa: o CaptchaField tinha onChange nas deps do efeito de mount e os call sites passavam arrow inline, então cada token emitido pelo Turnstile re-renderizava o pai e desmontava/remontava o widget em loop infinito (o captcha "piscando"). O callback passa a ser lido via ref, fora das deps. O container ganha faixa full-width arredondada na cor do widget, mantendo min-h-[65px] como reserva anti-CLS.
captcha_token nos endpoints de OTP (PR #1749) — d6b8a6bd9: o BFF passou a validar captcha no send e no resend do OTP de telefone, então o proxy /api/auth/register/validate-phone encaminha o token, o RegisterModal envia o capturado no step 1 e reseta o widget após cada consumo (token Turnstile é single-use); o register final pós-OTP usa token próprio, gerado em background durante o step 2. 8990f9db7 (PR #1751) deixa o captcha visível no step do OTP e o reseta após register falho.
Validação de formulário assumida pelo app — 9fb45a0af passa a usar noValidate nos forms de auth (validação própria em vez da nativa do browser) e 05ca32128 (PR #1721) corrige a validação de formato de telefone e de email no cadastro e no login.
Bolão / Copa dos Palpites (MatchX) removido por completo
Feature entregue em 08/06 e apagada em 23/07 (PR #1792) — f59121438 remove o módulo inteiro de todas as brands, e o commit é explícito de que não é desativação, a feature não volta: saem o card da home (app/components/bolao/*, bolao.server, /api/bolao/matches, config gamification/bolao no base e na 7k, types e locales bolao.json), o modal de ranking MatchX/Quiz (app/components/matchx/*, store matchxRankingModal, sweepstakes.server e as rotas /api/sweepstakes/* da API coreana), todos os pontos de acesso "Copa dos Palpites" (item da sidebar 7k, story, bloco comentado no campaign-widget, item morto no menu horizontal da donald), a flag feBolao7k com seus helpers, o maquinário de modalAction "matchx-ranking" e remoteGateMode dos sidebar-buttons, o deep-link #copa-dos-palpites e os env types SWEEPSTAKES_API_URL/SWEEPSTAKES_BRAND_ID.
PII hasheada em todos os eventos legados (PR #1782) — 5fc501c6e: email, phone, first_name, last_name e dob passam a ser enviados como SHA-256, mantendo os MESMOS nomes de campo (decisão de marketing: sem sufixo _hash). user_id e btag_id seguem crus. Pacote self-contained (gtm-identity-hash + gtm-user-params mais o efeito no useAnalyticsBootstrap). Consequência prática pra quem depura: ver email no dataLayer não significa mais ver um email — o valor ali é um digest.
anaLayer assume o container e o GTM primário é desligado — 50213444d (PR #1727) migra o anaLayer completo da stage pra main-7k, e 9b6f420c3 desliga o GTM primário na 7k (disableGtm: true no analytics.ts) porque o mesmo container GTM-59XBN5T7 estava declarado nas duas pontas e carregar as duas duplicaria os page_view. Agora ele carrega só via anaLayer, com l=anaLayer.
Ajustes do anaLayer — d0ac6e07e faz o download do container respeitar a estratégia engagement da brand (fila no boot, baixa na interação) e 7ccb8b01d corrige sign_up com usuário fresco mais o fallback sem hash na transição de auth.
Vai-e-vem de ferramentas — 3e85ca5d6 (PR #1774) reativa o GTM em produção na betpontobet e e37a4775c (PR #1790) religa o Pendo em cl-bet7k-com e ng-7k-bet, uma semana depois de ele ter sido desligado em todas as brands. Registrado aqui porque quem lê o changelog em ordem precisa saber que o estado final da semana anterior não durou.
Menus v2 (PR #1734) — bc4c65ac4 entrega três frentes config-driven, todas gated por override de brand: o hub ao vivo passa a responder 301 pra categoria-umbrella na família 7k (nova config routes/live-hub-redirect, throw redirect(..., 301) no topo do loader E do clientLoader, preservando query string pra não perder utm/gclid), as navs das 4 brands apontam direto pra categoria (sem hop e com active-state correto), o sitemap deixa de anunciar casino.live nessas brands e o remarketing classifica a umbrella como viewed_live_casino via getLiveHubCategoryHref() pra a audiência não ficar órfã. Também traz ordem curada de chips e busca nos hubs.
Busca — 1422572cd troca o breadcrumb pelo botão de fechar no padrão dos modais, 744cee25a (PR #1739) corrige a busca chegando vazia, ec1c3f963 reverte o SearchPill dos hubs após feedback de teste local. No sportsbook, f1d9e718c adiciona botão de busca e controles do SDK e 65b2e9875 faz a store de layout controlar a visibilidade do bottom nav quando a busca do SDK abre e fecha. 2df6a0306 (PR #1812) tira a Copa do Mundo dos menus e promove o Brasileirão A.
KYC (PR #1732) — 22cadf3f3 faz o liveness abrir sozinho quando a validação do saque cai no step de KYC, com guards claros: escopo restrito aos contextos firstWithdraw/withdraw, uma vez por montagem do step, só em entrada fresca do store (residual fatal mantém clique manual, sem loop de câmera) e sempre via iframe/SDK embutido, nunca window.open, pra não depender de gesto do usuário.
Layout: footer compacto e limpeza das páginas internas
Footer compacto nas páginas internas (PR #1720) — e985181b9 mostra só o bloco de compliance (selo AUTORIZADO mais texto de licença) com uma pill "Expandir" nas páginas de jogo, gamificação (vip/*) e conta (/user/*), em todas as brands e sem flag. O hook useCompactFooter detecta por rota via gameHref/routeHref com matching estrito — as bases de listagem de cassino são excluídas do match de jogo porque no config default o play-route é raso e colidiria (achado de revisão). bd63af1d1 conserta o modo compacto que não ativava na página single de jogo.
Footer stacked da família 7k — f9d72a3f3, 4a788a97b (ver mais único centralizado no mobile e expand global das colunas), 0bcd322b1 (reorganiza os selos) e e584fe4ad.
Página de jogo e área de usuário — 5bb4cd513 e b751a14c3 reorganizam o hero mobile, 4242ad521 remove a seção Estatísticas na 7k e move o "online" pra thumb, ab670e1bc remove o breadcrumb da área de usuário e põe o saldo em text-money, bd759d079 cria um degrau abaixo de 360px no header 7k com ellipsis garantido no saldo.
@cactus-agents/accounts 2.9.0 — core b575929 tipa o bloco _cached de /auth/user-profile (AuthUserCached, AuthUserCachedLastDeposit) e adiciona o selector getLastDepositAmount(user), que lê o último depósito aprovado do bloco get-last-deposit, converte de centavos pra unidades da moeda e devolve null quando a flag BFF_LAST_DEPOSIT_ENABLED do tenant está desligada, quando success é falso ou quando não há depósito — o caller cai no default. Base consome em ed4b47264 e 0af8c72f2.
@cactus-agents/i18n 0.99.0 → 0.103.0 na semana — core b9dc88b (tournaments.login_to_join nos 7 locales, 0.100.0) e 45814f8 (chaves do layout tabbed de torneios nos 7 locales, 0.102.0), consumidos no base por 72c0352ef, 598e53b18, c87c4067d e 64c72c4d0.
@cactus-agents/games — core d7bf0bc corrige uma expectativa stale de teste do transformTopWin: o teste esperava passthrough do username, mas o transform passa o valor por maskPlayerName (privacidade), então nome de palavra única vira os 2 primeiros caracteres mais **.
@cactus-agents/sports-rogue 0.27.0 → 0.29.0 nesta semana (6e3084b6f, 68be887e6, a33683ba5). Como de hábito, o pacote não vive em front-cactus-core e aparece só como bump de dependência no base.
betpontobet-bet-br adota o padrão visual split-shell da família 7k (PR #1793) — 6132ec90f cria layout/composition.ts novo (split-shell, header-stacked, sidebar-accordion 104/320, mobile-nav-fab-logo, footer-stacked, section-title-gradient, cta-signature), migra os widgets pro padrão 7k (cta-button, top-games, game-card stacked, tournament panel, mission stacked, provider logo-only, quick-access-menu, game-stats, game-winner, home-leagues square) e recria as decorações com a marca "b" em rim-light na identidade roxo e laranja. O header sai do laranja sólido pro escuro unificado (#13051c) com laranja nos acentos e CTAs; token accent adicionado; container em 1440px. f3da46f9b remove em seguida o override morto de theme/mobile-bottom-nav (a nav estilo betano ficou fora de uso com a adoção do split-shell).
cl-bet7k-com:4dee2bdac (PR #1801) trata bet7k.cl como produção via PUBLIC_DOMAIN no SEO; 06d888131 dá título e meta em ES pra /deportes; e18906fd9 traduz o chip de categoria "Jogos da Pragmatic" pra ES; 52d43d4e3 unifica a máscara de nome nos widgets de vencedores da home; ad2ba93e7 (PR #1802) aproxima o presente do logo na sidebar desktop; 6f519b633 pré-seleciona $20.000 (CLP) ao abrir o modal de depósito.
7k-bet-br:b7e8f9cdb fixa o tom do dinheiro no #d6ed16 do legado. betpontobet-bet-br:3b6c43e6a atualiza o texto da portaria no footer legal (PR #1795).
Valores sugeridos baseados no último depósito (PR #1766) — 7f9ecc874: com _cached["get-last-deposit"] presente no user-profile, os chips do modal de depósito passam a ser multiplicadores sobre o último depósito do usuário — o 1º chip é o próprio valor e os demais são configuráveis por brand via o novo campo opcional depositConfig.lastDeposit.multipliers. Na família 7k: 150% (default, marcado como HOT), 300%, 500% (HOT), 1000% e 2000% (HOT), com overrides de deposit.ts criados pra cl, fi e ng. resolveDepositShortcuts/buildLastDepositShortcuts arredondam pra unidade inteira, filtram por mínimo e máximo do método e deduplicam; sem last-deposit ou com tudo fora do range, os chips estáticos voltam. Brands sem o campo não mudam de comportamento.
Moeda por brand nos overrides — cd407df3f faz os overrides de deposit carregarem só a moeda da própria brand.
RUT no depósito — 2e4499228 valida o RUT no front e mostra erro amigável no modal de depósito das brands CHL.
CACHE_GENERATION trocada em todo deploy — ops 5d877cb passa a mudar a geração em cada deploy pra forçar limpeza de cache, e ops 2f58a21 chama purge antes E depois do deploy; ops 8c20e76, 4790a40, 4ef9182 e 9df6b24 são os ajustes de rota desse mesmo trabalho. Três bumps formais de geração na semana: ops 9abef9f (apcsrasj-v16 → v17), ops 6bbcaca (v17 → v18) e ops 41cd057 (v18 → v19).
Casca SSR ampliada de 7 pra 30 dias — ops 34a3467 (envs CL: cl-bet7k-com e bet7k-cl), ops d7eddc2 (stages cl e 7k), ops 8a90664 (brands ng e fi, prod mais stages) e ops 372276e (prod-7k-bet-br). É o número que sustenta o experimento de casca SPA descrito acima.
public_domain por env — ops 642f70e injeta o campo como PUBLIC_DOMAIN, que é o que o 4dee2bdac do base consome pra decidir origem canônica de robots e sitemap. Do lado do base, f76058a88 (PR #1811) força https no origin público de robots e sitemap.
Branch por env em movimento — ops 5da617d aponta o stage da betpontobet pra main-7k; ops 9e10fa1, 8acaedb e f139be2 fazem o vai-e-vem de default_branch dos envs CL entre main-7k, main-7k-revert e main-cl-bet7k-com.
Testes — 2ce36eff6 elimina o flake de timeout em imports dinâmicos na suíte. Sem entregas em front-tests-e2e, front-service-api, front-service-api-dev-proxy e fecadm nesta semana.
Os comentários do código passam a apontar pra doc — f1bf09cbf e bc50bc915 fazem os gates de auth do base referenciarem state-management.md, pra quem tropeçar num gate no código achar a regra em vez de reinventá-la.
As três especificações de identidade CHL (4f3709ec3, e61beba67, 857634714) vivem em docs/superpowers/specs e são a fonte usada na semana seguinte, quando o fluxo vai contra o BFF real.