Janela desta entrada — cobre 03/08 a 09/08 (25 commits em main-7k). É a entrada mais recente do changelog; 10/08 ainda estava em curso quando ela foi escrita, e os dois commits daquele dia que fecham a poda de environments estão anotados no fim, em ## Infra / CI/CD.
O Cloudflare Images codifica o transform no PATH, então cada combinação de largura, altura e recorte é um RECURSO distinto (PR #1876) — 1b6b8e904 parte dessa constatação: cache-key de browser própria, entrada de cache CF própria e download próprio pra cada variação. O app tinha 12 superfícies pintando game.image com 4 recortes e 10 ladders diferentes, nenhuma compartilhando nada. Medido no 7k mobile (iPhone 14 Pro Max, DPR 3, 3G): o card do carrossel pedia width=240,height=300 (10,0 KB) e a página de jogo pedia width=320,height=400 (13,5 KB) — 23,5 KB pra mostrar a MESMA arte duas vezes, com o segundo request entrando frio no 3G do usuário e a arte piscando ao abrir o jogo.
Pior: os degraus grandes eram upscale. O master da arte de jogo no CF é 300x350 (amostrado sobre 12 assets reais da biblioteca), então 320, 447 e 480 inventavam pixel — width=480,height=600 devolvia 21,5 KB de nada, o dobro do degrau que a listagem já tinha em cache. O teto útil é ~300px de largura.
O que passa a valer — ~/utils/game-artwork centraliza ladder e fallback num perfil único e cada superfície declara só o seu sizes, que é o slot de layout real. Não há mais recorte no servidor: o enquadramento vai pro CSS (object-cover numa caixa de aspect fixa), então card 4/5, poster 59/79 e thumb de proporção natural passam a compartilhar o MESMO arquivo. O ladder default vai de [144,216,288] pra [128,192,288] — o degrau do meio serve DPR 1 e precisa ficar logo acima do maior slot (186px, a thumb do preview da página de jogo) sem nenhum degrau entre ele e o menor slot de card de desktop (140px); com 216 o card pegava um degrau e o hero era empurrado pro seguinte, baixando a mesma arte duas vezes no desktop. As 5 brands size: "compact" passam a [128,192,240], cortando ~25% dos bytes e da memória de bitmap decodado. Fundos borrados e reflexos reusam as props EXATAS da thumb em vez de baixar uma segunda variante recortada só pra ficar sob um blur().
Guarda-chuva contra regressão — 11 testes de perfil mais uma auditoria de TODAS as brands (56 casos) que falha se alguém reintroduzir um degrau entre o card e o hero, inclusive em brand nova. THUMB_ASPECT_VALUE foi removido porque existia só pra alimentar o recorte no servidor e mantê-lo era convite pra refragmentar.
A convergência que faltava (PR #1877) — a97d91545: o perfil único convergia em DPR maior ou igual a 2 (todo celular real) e em DPR 1 no desktop, mas não em DPR 1 com viewport estreito, onde os slots divergem de verdade (o card declara 115px e a thumb do preview ~152px, com o degrau 128 exatamente no meio). O fix faz as superfícies da página de jogo declararem o slot do CARD no mobile: GAME_PREVIEW_ARTWORK_SIZES passa a espelhar os breakpoints do card e o GamePageHero da donald vai de 131px pra 128px. É uma sub-declaração deliberada de ~19% — em DPR 1 estreito a arte vem 128px numa caixa de ~152px, imperceptível.
Identidade Chile chega à conta, ao depósito e ao saque
Do cadastro pras três telas de usuário logado (PR #1881) — e260826e6 leva a validação de identidade CHL (RUT mais nascimento via DIDIT), que existia só no cadastro, pra conta, depósito e saque. Os três rodam a MESMA sequência, sempre gateada por não haver kyc_validated_at: /validate, depois /confirm-birthdate, depois /verify-identity, e só então o fluxo segue.
O passo 3 conserta um bug de dados real — ele substitui o caminho legado de persistência (storeDocument em store-mex-document mais updateProfile do nascimento), que FALHAVA pro RUT porque aquele endpoint é do México e exige 11 caracteres. Era a causa de documento gravar com type: "cpf" em conta chilena. A rota proxy autenticada nova é api/user/documents/verify-identity, que resolve o usuário pela sessão e manda number mais birth_date; o RUT vai sem separadores, e o helper remove só ., - e espaço — nunca um replace(/\D/g), porque o dígito verificador do RUT pode ser K e isso mandaria um documento diferente pro backend. useChileIdentityPersist passa a ser o ponto ÚNICO de persistência dos três consumidores.
Na tela de conta — a7f693564 cria o ChileIdentityValidationBlock reusável sobre RutValidationField e BirthDateValidationField, com dois modos: com nascimento conhecido valida só o RUT e confirma a data; sem nascimento, orquestra RUT e data num clique, espelhando o cadastro. f464d610c deixa o resumo de identidade sempre visível e esconde o lápis quando não há ação; 9e79c81ad acaba com a duplicidade da data de nascimento, que passa a existir só no DocumentSection; 7aecec423 esconde o RUT legado do backoffice (requestDocument) no cadastro CHL/DIDIT.
Sinal de validado passa a ser explícito — f7f955683 cria o hook useChileIdentityStatus derivando o estado por presença do documento, e bec0214b9 corrige a semântica em seguida: o sinal de identidade validada vem de kyc_validated_at, não de presença.
O gate nunca disparava em produção — 8a02df8fb é o achado mais importante da semana no fluxo: a detecção de ProntoPaga usava resolveCanonicalMethod(slug), que colapsa o slug pro prefixo conhecido mais longo, então "webpay-prontopaga" virava "webpay" e nunca casava com "prontopaga". O método aparecia como WEBPAY-PRONTOPAGA na tela e ainda assim não gateava. A causa é conceitual: o slug descreve o MÉTODO, não o PSP — o mesmo método é servido por PSPs diferentes (webpay-prontopaga e webpay-pagsmile são os dois "webpay"). Quem identifica o PSP é hub_payment_methods.provider_code do /providers, exposto pelo core como hubPaymentMethods.providerCode. isProntoPaga() passa a receber o PaymentProvider e ler o providerCode; brand legada sem payments v3 (hubPaymentMethods null) devolve false. Confirmado contra o /providers real da brand CL: dispara em webpay-prontopaga (depósito) e bank-transfer-prontopaga (saque).
O seed passa a vir de toda listagem (PR #1876) — 88227b346: o clientLoader da página de jogo monta o seed (jogo já conhecido nesta sessão) com um lookup síncrono em gamesBySlug, e com seed o fallback pinta arte, nome, provedor e fundo IMEDIATAMENTE, porque a arte já está no cache do browser. Só quatro caminhos alimentavam esse cache (hydrateHome, hydrateGameDetail, setSearchResults e os relacionados da própria página) — cacheGames era chamado num ÚNICO lugar do app. Grade de provedor, grade de categoria, favoritos e recentes ficavam de fora, então home para jogo e busca para jogo funcionavam, mas provedor para jogo caía sempre no skeleton completo.
"Outros Provedores" com skeleton e streaming (PR #1878) — 8c09c4117: o título era o genérico "Provedores" (chave do core, usada nas listagens) e passa a ser "Outros Provedores", com chave NOVA nos overrides de locale do base nos 7 locales, respeitando o termo que cada idioma já usa. O ProvidersCarouselSkeleton nasce no MESMO arquivo do carrossel pra geometria e header não divergirem, e o header dele é o SectionTitle REAL — título, emoji e "ver todos" não dependem de dado, então não faz sentido fingi-los, e a altura sai exata de graça. As setas entram desabilitadas, que é o estado verdadeiro. 007cd6acb completa: a row "Mais jogos de" ganha o emoji 🎰 já usado pelas rows de jogo, e a lista de provedores volta a ter header (o componente era chamado sem props e o header dele é condicional).
Pré-carregamento do card de preview estabilizado — b9899a105: o GameShellFallback replicava a caixa preta e o card mas NÃO a imagem de fundo, que só entrava no DOM quando o gameShell resolvia — a thumb pintava na hora (cache) e o fundo chegava segundos depois, lendo como página lenta. Agora o fallback pinta o fundo com as MESMAS props da thumb do seed, mesma URL e portanto zero download extra.
Breadcrumb reativo em navegação SPA (PR #1870) — 2e9257f71: em navegação SPA o game do clientLoader é uma Promise pra sempre e o trail dependia só do seed, então jogo nunca visto numa row, busca ou relacionados (por exemplo a grade paginada de provedor, que não alimentava o cache) chegava com seed nulo e o breadcrumb ficava vazio PERMANENTEMENTE, mesmo depois de o jogo resolver — só voltava com F5, nova aba ou revisita. Agora o trail também lê o slug reativamente da store.
Pulo de ~2,5px no carrossel (PR #1879) — cf3c5a9e4 explica um bug sutil de content-visibility: o track é flex com align-items: stretch, então a altura da linha é a maior altura HIPOTÉTICA entre os items, e pra um item size-contained essa altura vem do contain-intrinsic-size e não do conteúdo. Com auto 150px, um card real menor que 150px fazia os items off-screen inflarem a linha; ao rolar, o auto memorizava o tamanho real e a linha encolhia de volta.
87ffe69fa remove a animação de entrada da seção "Grande vitória" (o animate-fadeIn rodava no primeiro paint E a cada troca de período, porque key={resolvedPeriod} remonta a subtree, e os cards "subiam" toda vez). O key ficou de propósito — removê-lo mudaria comportamento, não estilo.
"Já paguei" não é mais uma conversão (PR #1868) — 428067a7c: o clique em "Já paguei" chamava o mesmo onApproved do polling, disparando pix_confirmado, pix_confirmado_ftd, first_deposit e purchase (Google, Facebook, Taboola), mais AppsFlyer S2S e purchase/same_day_ftd no anaLayer — sem pagamento confirmado. O botão passa a usar onAlreadyPaid (finalizeDepositFlow), que só atualiza wallet e profile e fecha o fluxo; o tracking fica exclusivo do polling quando o status volta approved. Mesmo fix nos botões idênticos de BankTransfer (NGA) e Wallet/MACH (CHL). Quem analisa série histórica de conversão deve tratar os dados anteriores a este commit como inflados nesses eventos.
Pendo identifica usuário sem cactus_id (PR #1875) — 722d46326: contas da Nigéria não recebem cactus_id (é gerado só pós-KYC), então o visitor.id ficava vazio e o usuário logado não era identificado; na brand NGA ele passa a cair no AuthUser.id numérico. 51c1466c9 generaliza no dia seguinte, a pedido: o fallback vale em QUALQUER brand quando o usuário logado não tem cactus_id, e o gate por país sai. O cactus_id segue com precedência quando existe e o não logado continua anônimo.
Título da home restaurado do snapshot (PR #1869) — fc287892d: entrar direto numa rota e navegar por SPA pra home compunha o título com prefixo vazio, produzindo um sufixo órfão. A whitelist do localStorage não persistia campos de SEO, o clientLoader defaultava seoTitle: null (não-Promise, fora do alcance do useDeferredSeoTitle) e o meta() compunha composeFullTitle("", brand). Fix em duas camadas: composeFullTitle com título vazio cai pra brand sozinha em qualquer rota, e a whitelist passa a persistir pageDescription, seoTitle e seoDescription, gateados à presença de campo core, então hub, ao vivo, categoria e provedor seguem sem gravar nada (foto parcial quebraria a restauração deles).
@cactus-agents/utils 1.3.0 — quality e format nunca em querystring — core d2fbcac é a contraparte obrigatória do perfil único de imagem: getImageUrl() só usava a sintaxe de path (…/width=240,quality=60,format=auto) quando havia height ou fit; com apenas width caía numa forma de querystring (…/w=240?quality=60&format=auto) que o Cloudflare ignora por completo, devolvendo a imagem na qualidade default da variante. Medido contra a CDN real em 04/08: …/w=240, …/w=240?quality=60 e …/w=240?quality=20 devolvem bytes IDÊNTICOS (16.323 B), enquanto …/width=240,quality=60,format=auto devolve 9.207 B pela MESMA imagem — 43% de pixel pago à toa. Afetava toda superfície que passa só width: thumbs de lista, Top10, logos de provedor, selos do footer e logo do header. Agora, com width setada, o transform inteiro vai no path, e fit só é emitido junto com height.
Bump no base — 7010a029c traz @cactus-agents/utils pra ^1.3.0 e registra que é requisito duro do perfil único: sem ele, as URLs width-only caem na forma de querystring e o resultado é regressão (16,3 KB onde a forma de path entrega 6,7 KB). No mesmo lockfile, @cactus-agents/games foi de 2.2.0 pra 2.2.1 como patch incidental do changesets, que repontou a dependência interna de utils; o range declarado ^2.2.0 já cobria, então só o lock mudou. Verificado com resolução de REGISTRY, não source-direct do core local: typecheck:ci verde e 1.822 testes em 188 arquivos.
cl-bet7k-com — de9bba580 (PR #1748) move a pré-seleção de $20.000 pro override da brand: com a main-7k atualizada, cl-bet7k-com passou a ter override COMPLETO de deposit.ts, e como override de brand é substituição de arquivo e não merge, o default: true que vivia no arquivo raiz virou código morto pra ela. O override também traz os sugeridos dinâmicos por last-deposit com 150% pré-selecionado. É o caso de escola da regra: ao adicionar campo a uma config que tem overrides, propague pra todos, senão a brand recebe undefined — ou, como aqui, simplesmente não vê o valor.
7k-bet-br — 7c812d23f liga o hideHeader novo no slug de top-games pra a curadoria do BFF em api_new parar de imprimir o título "Pagou Muito", que o backoffice não tem opção de remover.
428067a7c desacopla o botão "Já paguei" do tracking de conversão (detalhado acima).
Cards informativos saem do formulário de saque (PR #1882) — e5cfb70ba remove os três boxes de topo (MÉTODO, MÍNIMO, MÁXIMO) do WithdrawForm, por pedido de produto, em todas as brands e sem flag: o método já aparece no MethodSelector logo acima e os limites continuam comunicados no painel "Regras de saque" e nas mensagens de validação. minAmount e maxAmount seguem sendo usados na validação e no cálculo do effectiveMax — só a exibição saiu, e o helper stripZeroCents, que existia apenas pra esses cards, foi removido junto. O stepper da donald não tem esses cards e não é afetado.
Geração de cache em apcsrasj-v21 — ops 688df31 faz o bump v20 para v21 em 03/08. Vale repetir onde está a verdade operacional: config/cache/generation.yml no front-ops é a fonte rotacionada pelo workflow bump-cache-generation via injeção de env; o token em platform-cache/src/generation.ts é só o fallback in-source pra quando nenhuma env é injetada. Como o valor mudou 21 vezes em dois meses, prefira consultar o arquivo a memorizar o número.
Cutover pós-warm no pipeline de deploy — ops 93ef753 (07/08) adiciona um estágio OPCIONAL ao deploy.yml, gated pela flag run_warm_cutover por env e ligado no stage-cl-bet7k-com-cactusgaming-tech: faz o patch e o deploy de um entry worker próprio, roda um collector de warm na AWS via SSM (documento front-web-warmup, alvos por tag de tenant, com credenciais assumidas por OIDC), depois bumpa CACHE_GENERATION_SECRET pra um timestamp puro em milissegundos e por fim dispara o purge-all multi-camada. A ideia é entregar uma namespace de cache nova já aquecida em vez de vazia. Sem a flag, o deploy segue idêntico.
Poda de environments (arrasto de 10/08, um dia depois desta janela) — ops e920c2b remove 7 environments do web-base que não eram mais usados (dev-betpontobet-bet-br-cactusgaming-tech, dev-state77-com-bluetec-live, performance-7k-bet-br-cactusgaming-tech, performance-betpontobet-bet-br-cactusgaming-tech, stage-cache-7k-bet-br-cactusgaming-tech, stage-spa-7k-bet-br-cactusgaming-tech e react-c4ctus-com) junto dos namespaces de branch que só existiam por causa deles. Os namespaces suportados passam a ser main*, stage* e sports* — os prefixos dev* e performance* saíram. Efeito colateral tratado no mesmo commit: 7wins-cc era o único env fora do padrão (default_branch: dev-7wins) e passa a main-7wins. Os Workers correspondentes seguem no ar na Cloudflare; a remoção deles é manual e não faz parte do commit. Do lado do base, 3f3ee4389 (PR #1888) sincroniza o dropdown e os filtros de branch do ci-deploy, deixando-o idêntico nas 12 branches que rodam algum environment, com os 28 environments que o web-base tem hoje no front-ops — mais 5 no web-external, 3 no docs e 2 no affiliates.
Sem entregas emfront-tests-e2e, front-service-api, front-service-api-dev-proxy e fecadm nesta semana.