Semana 11–17/05 — Álbum de figurinhas entra no base
Módulo album portado do harness pro host — 86435c4a9 traz o drop-in modules/album do repositório do sticker-book pra app/components/album/ (59 arquivos: Album.tsx, AlbumModal.tsx, páginas Missions / CheckIn / Stickers / Terms / PackOpener, context, hooks, reducer, types e utils) já cabeado na infraestrutura do base — rota gamification.album → /vip/album registrada em todos os paths.ts de override, loader com auth gate e AlbumBootstrap. biome.json passa a excluir app/components/album/**, porque é código de módulo terceiro. Branch feat/album-integration.
Camada de proxy e serviço server-side — cinco rotas novas em app/routes/api/album/ (open-pack, open-all-packs, checkin, claim-prize, claim-featured) e o serviço app/services/album.cache.server.ts, que dispara um burst paralelo de 5 endpoints do BFF do álbum (stickers, packs, dias de check-in, featured, missions) chaveado na env ALBUM_API_URL e retorna null quando a env não está configurada — feature simplesmente não aparece em vez de quebrar a rota.
Overrides de tema por marca — 7k (navy + amarelo), cassino (azul + ciano) e vera (verde + lima) ganham config própria; o default em app/config/gamification/album.ts fica num slate escuro neutro.
Dois fixes de integração no mesmo dia — 24d73ebe1 remove um import circular introduzido nos overrides de marca e c2db1cd2b move a rota pra fora do layout VIP, porque a página não deve depender de gamificationConfig.modules pra existir.
Remarketing, tracking e o novo registry de analytics
Remarketing first-party ligado nas 13 marcas que ainda não tinham — 3096d1963. A vera-bet-br já estava ativa no commit anterior; esta passada liga o restante com cookie name e domain dedicados por marca (rmk7k em .7k.bet.br, rmkbpb, rmkcl7k, rmkfi7k, rmkng7k, rmkpb, rmkphst77, rmkptst77, rmkrj, rmkst77, rmkx2b, rmkcasateste, …). Nomes curtos por causa do payload em header HTTP, prefixo de país nas variantes regionais pra isolamento explícito, cookieDomain sempre .${ORIGIN_DOMAIN} pra não vazar entre marcas irmãs, e nomes neutros por causa da regra de neutralidade em cookies expostos ao browser. sendToBff: false em todas — o backend ainda não recebe external_id; pushToDataLayer e exposeOnWindow ligados, então o GTM recebe rmk_ready / rmk_audience_tag e window.__rmk fica disponível pra scripts custom.
TTL do cookie de tracking sobe de 7d pra 30d — cc9c8b9eb. TRACKING_COOKIE_DEFAULT_MAX_AGE em app/utils/tracking.ts vai de 604800 pra 2592000 segundos; os três overrides que pinavam 168h passam a pinar 720h (7k, cassino, vera) e betpontobet fica em 240h por decisão própria. Pedido de marketing: visitante que volta dentro de 30 dias conta pra campanha que originou o tráfego em vez de virar atribuição direta. Cookies não-tracking (jwt 30d, consent 365d, referrer 30d, remarketing 365d) ficam intocados.
Registry tipado de eventos + matriz declarativa de destinos — 604b6d3e5 consolida os eventos clássicos do MetricEvent (sign_up, first_deposit, purchase, withdraw, deposit_initiated, …) e as audience tags do RemarketingCapture (viewed_casino, clicked_register, ftd_completed, multi_deposit, …) num registry único em app/analytics/events.ts, com o fan-out por canal declarado em app/analytics/destinations.ts (GTM, Meta Pixel, Kwai, Taboola, TikTok, Mixpanel, Smartico, Webtrends, cookie de audiência, CAPI do BFF) e um dispatcher central em app/analytics/dispatch.ts que lê a matriz e chama cada adapter não-nulo com try/catch por canal. O TypeScript força exaustividade: evento novo sem destino mapeado quebra o build. pushMetricEvent virou shim de back-compat — comportamento observável idêntico ao pré-refactor.
Quatro flags MVP novas no mesmo commit — played_demo (clique no Demo do GameIframe), started_register (primeiro dígito em e-mail/documento do RegisterModal, fire-once via useRef pra não floodar o dataLayer), viewed_app_install_banner e scrolled_to_payment_seals. 45 testes novos, suíte total em 876 passando.
Pendo ligado em 7k e vera, com visitor.id migrado — e0cfeebb1 reaproveita o mesmo agent token do cassino e segue a convenção de accountId domínio-com-hífen. O usePendo passa a ler o identificador estável de conta enviado no topo de /auth/user-profile em vez do AuthUser.id numérico legado; usuário autenticado sem esse campo continua caindo em anônimo, igual ao comportamento pré-mudança.
Conteúdo WordPress unificado e Central de Ajuda redesenhada
Um serviço só pra seis áreas de conteúdo — 15ac491f0 aposenta a config fragmentada appearance.links.{blog,promotions,promotionsWpApi,promotionsIframe,helpCenter} e cria o ContentsConfig, que serve blog, promoções, notícias, jogo responsável, ajuda e FAQ do mesmo backend WordPress por marca. O serviço novo app/services/contents.server.ts (resolveContentArea, fetchAreaPosts, resolveCanonicalCategorySlug) é compartilhado pelas seis áreas com uma única entrada de platform-cache por marca (contents:categories:all). Slugs de URL localizados por marca via getAreaUrlSlug / getAreaForUrlSlug em app/utils/content-routes.ts.
Central de Ajuda redesenhada — hero de busca (HelpSearchHero), grid de categorias (HelpCategoryGrid alimentado por app/config/content/help-category-icons.ts), cards minimalistas com título + chevron e line-clamp-2, 24 itens por página. Grid vira 4 colunas sem sidebar e 3 com sidebar via getGridColsClass em ContentListSection.
FAQ passa a vir do WordPress — o adapter app/services/faq-wp.server.ts extrai pergunta/resposta do conteúdo <details>/<summary> do WP; o FAQ hardcoded fica pra trás por back-compat e o legado do 7k foi esvaziado de 844 pra 21 linhas. Sitemap unificado em /sitemap/:area/:filename, aposentando sitemap-blog e sitemap-promotions.
URLs achatadas em todas as seis áreas — d1fd7b8ae tira o segmento de categoria dos posts e o literal categoria dos listings: /blog/categoria/cassino → /blog/cassino, /blog/cassino/fortune-tiger → /blog/fortune-tiger, /promocoes/bonus/cashback → /promocoes/cashback, /ajuda/artigos → /ajuda/categorias. Um resolver server-side (loadContentEntry em app/services/contents-page.server.ts) checa primeiro se o :slug é categoria conhecida (cache hit, custo zero) e só depois busca como post; categoria vence em colisão. Redirects 301 do formato antigo unificados em app/routes/legacy-content-redirect.ts.
Blog com URLs de categoria amigáveis — b6e55748a (na véspera do achatamento) migra ?category=ID pra /blog/category/:slug, filtra posts pela categoria-pai (subcategorias incluídas) e adiciona sidebar de subcategorias na página de promoções atrás de flag.
SEO — dados estruturados, sitemaps de imagem e canonical
Fundação de JSON-LD (fase 1) — ce0deefcc cria app/types/jsonld.ts com os tipos canônicos de cada schema e app/utils/jsonld.server.ts com builders puros e absolute-URL strict: buildBreadcrumbJsonLd, breadcrumbsToJsonLd, buildArticleJsonLd, buildFaqPageJsonLd, buildCollectionPageJsonLd, buildVideoGameJsonLd, buildEventJsonLd, buildOrganizationJsonLd, buildWebSiteJsonLd, buildWebPageJsonLd e o utilitário absolutize. author sempre como Organization.
Fase 2 — BreadcrumbList universal + Sitelinks Search Box — 9d90777ea emite BreadcrumbList em todas as rotas públicas que ainda não emitiam, liga potentialAction.SearchAction no schema WebSite do _layout.tsx (pré-requisito do Sitelinks Search Box), troca twitter:card de summary pra summary_large_image e mata a duplicação de facebook-domain-verification — antes o root.tsx e o _layout.tsx emitiam a mesma <meta> duas vezes; agora só o layout emite, com fallback seoConfig → analyticsConfig.
Fase 3 — família Article para posts WP — ea90e4ae6. Fases 4 a 6 — 41af2a565 (VideoGame com publisher nested, identifier, genre e AggregateRating dual-source: editorial curado ou derivado dos votos reais com threshold de ≥10 votos), c9e9ddc6c (BreadcrumbList no detalhe de torneio + seoConfig ganha contactPoints, identifiers pra licenças regulatórias, disambiguatingDescription e foundingDate).
Schema do jogo migra pra SoftwareApplication — 57b4ab6ff resolve os dois erros que a auditoria Semrush apontou em 2.759 páginas de detalhe de jogo: AggregateRating ausente em 2.757 (o Google só emite o rich result de estrelas em SoftwareApplication, não em VideoGame) e additionalProperty inválido em 1.823 — este último confirmado como sendo apenas do stack Vue legado, capturado pelo crawl via URLs /cassino/jogar/* que redirecionam. A rota agora sempre emite um rating, caindo num piso seguro 4.0 / 1 / 5 / 1 quando não há valor editorial nem 10 votos. Itens de listagem seguem com itemType: VideoGame.
Google Image Sitemap extension inline — 6e247960c estende todos os sitemaps existentes com o namespace xmlns:image, emitindo só <image:loc> dentro de <image:image> (as tags depreciadas na limpeza do Google de 2022 são ignoradas mesmo quando o caller passa). Ganha também o helper resolveImageDeliveryUrl(), que respeita o imageDefaults.useProxy do core e resolve para /images/…, /cdn-cgi/imagedelivery/… ou a URL direta em dev, além de um toggle de preview de XML cru.
jsonld.server.ts renomeado pra jsonld.ts — 7328fca07: o sufixo .server impedia que meta() importasse os builders. 6d95a331e concatena o pathname no hreflang e no canonical (self-reference), b395a2500 marca páginas legais como noindex,nofollow, e 89798f04c + a5acd6545 adicionam alt/title nas imagens de conteúdo e nas imagens semânticas (com 77c363419 revertendo o que tinha vazado de escopo).
Seção nova, brand-overrideable ponta a ponta — 4ab13b43d adiciona listagem /perfis + detalhe /perfil/:slug, com tipos em app/types/profiles.ts (ProfileEntry, dois layouts de detalhe — facebook-classic e compact-hero — e dois variants de card — cta-stacked e simple). O padrão é agregador por override: cada marca solta um arquivo TS por perfil em overrides/<brand>/app/config/profiles/profiles/ e o índice agrega via import.meta.glob. Widget de sidebar (app/widgets/sidebar-profiles/) integrado nos quatro variants de sidebar, row de home (profiles-row), <PageDescription> pra conteúdo SEO longo e /sitemap-profiles.xml gerado dos perfis ativos. O brand-resolver.mjs foi estendido pra resolver pasta → index.{ts,tsx}.
Curadoria inicial e ajustes de layout — 83352af42 (coleção Fortunes, expansão do conteúdo SEO, bootstrap da vera), 9a62a3ac9 (7k: Hulk, Craque Neto, Xand Avião, com keywords de gamificação disjuntas), 2b8c0d71f (refino do ratinho no cassino), fd37a682c seguido de 924582449 — o variant nasceu com nome de marca e foi renomeado pra centered-spotlight, alinhando com a regra de variants descreverem padrão e não marca. cb58a942a alinha container e título do widget ao padrão da sidebar.
Ativada só em 7k-bet-br e cassino-bet-br: featuresConfig.profiles.enabled fica undefined no resto, e rota, widget e row se auto-gateiam — omitir a flag custa zero em runtime.
Etapa de OTP por SMS antes de criar a conta — 1f0492d5d (cherry-pick squashed do PR #566). Quando o BFF manda register_validate_phone: true — ou, pra QA, quando authFeaturesConfig.forceRegisterValidatePhone está ligada, hoje só em ng-7k-bet — o "Criar conta" passa a chamar /bff/otp/register/send, desliza pro passo 2 dentro do mesmo modal, e o registro original só sobe com validate_code anexado. Depende do PR pareado do core (types/brand/api-client/i18n).
Fluxo de confirmação de e-mail/telefone por link migrado do legado — fb55fc603. A rota /user/validate/:type/:token existia no stack novo mas tinha sido reaproveitada pra recuperação de KYC: clicar no link de verificação enviado pelo BFF abria o KycModal em vez de confirmar o e-mail. Agora a rota bifurca por type: email/phone renderizam a LinkConfirmationPage nova (espera hidratação de auth, abre login se deslogado, checa validate_email_at / validate_sms_at e faz POST /api/validation/link-confirm), e o resto segue no caminho de KYC. A rota de proxy app/routes/api/validation/link-confirm.ts encapsula POST /bff/validate-confirmation.
Cache de perfil invalidado depois de toda mutação — 35d75805c. Regressão reportada nas preferências de comunicação: alterar um toggle, navegar e voltar trazia o valor antigo, e só um hard refresh mostrava o novo. Causa: getAuthForRequest em services/auth.server.ts cacheia o perfil do BFF por token num Map com TTL de 10s pra deduplicar requests no mesmo isolate do Worker — o merge otimista do SDK atualizava a UI, mas o loader do _layout relia o perfil velho do cache. eb608f59d estende a invalidação pros limites de depósito/aposta/perda/tempo.
Correções de formulário e tokens — bf439b93f limpa erros de campo no submit do login antes de nova tentativa, 327dad99c evita labels de erro truncadas no registro e 41340d5f0 troca uma cor hardcoded no botão de colapso por tokens do tema.
Proxy /api/cactus-sportbook/* no React Router — 003b9cd25. O iframe do sportsbook First chama endpoints tipo GetBalance?token=… direto no domínio da marca; em produção o middleware do Worker (handleCactusSportbookProxy) já interceptava antes do React Router, mas dev local e ambientes sem CF Workers ficavam descobertos. A rota espelha o middleware exatamente: só GET (405 no resto), tira cookies de auth antes de encaminhar (o endpoint do BFF é público), propaga X-Forwarded-For a partir de CF-Connecting-IP, repassa todos os headers da resposta e força Cache-Control: no-store. ab480a9b5 injeta os headers de origin-domain e tenant; 4c7a07bff troca a implementação manual por ApiClient.proxyRaw.
Paridade de URL do iframe First com o legado — d55a2998d sincroniza a URL do iframe com o estado do React Router, e38ad17aa preserva URLs amigáveis e navegação suave, e fabfefd02 fecha paridade total (path v3 + query + #silent). 626eba4ae troca o deep-link de "regras de aposta" pra postMessage e 7f0205982 volta pro rewrite de src limpando o pathEntry pendente — a saga continua na semana seguinte.
~60 URLs legadas do 7k indexadas no Google mapeadas pra rotas canônicas — f8c1a1241, escopo restrito ao override do 7k por decisão de produto: páginas legais top-level, /pagina/fidelity-program (programa descontinuado), junk top-level, /_nuxt/*, /403, prefixo /ar/* que faltava na lista base, LPs (/Neto, /craque-neto → /perfil/craque-neto; /black-november → /promocoes), listagens de provider de um segmento e promoções/posts com slug morto resolvidos por melhor equivalência.
Três regras novas no middleware do Worker — ee9add361: /deploy-* em qualquer host vira 301 pra home (cobre centenas de URLs /deploy-<gitsha>-<chunkhash> indexadas nos CDNs legados), credenciais vazadas no path (/:user@email:senha) respondem 410 Gone — sinalizando deindexação permanente sem expor redirecionamento — e o host de atendimento ganha mapeamento artigo-a-artigo pro help center por substring do slug do Zendesk, devolvendo null quando não casa pra deixar o Zendesk responder. A regra de /deploy-* e a de host de atendimento dependem de bind de rota no lado de ops — o que acabou não acontecendo, e a segunda foi removida como código morto na semana seguinte.
Catch-all de paths malformados entra e sai — 06255acbd fez o loader de routes/$.tsx interceptar paths de caractere único (/$, /&, /;), /ref=<código> → /?ref=<código> e URLs com o próprio domínio dentro do path. Foi revertido em 18/05 por de2c936c1: o impacto era menor que o overhead de mexer no catch-all de 404. Os demais redirects do PR (overrides cross-brand e as regras de middleware) seguiram intactos.
Redirects pontuais: 35571cdee (/u e /.well-known/passkey-endpoints), 6ffb417bd (/user/referrals → user.refers), 1741d9528 (/atendimento → help center). e52ba07ee desabilitou /download-app em cassino e 7k de forma reversível e da9787797 reverteu o merge no mesmo dia.
Carousel quebrado em hooks composáveis — 9cab3e753 transforma o arquivo único de ~800 linhas na pasta app/components/ui/Carousel/ com useCarouselEdgeState, useCarouselDrag, useCarouselScrollControls, useCarouselArrowPolicy e constantes/classes isoladas, mantendo a API pública 100% compatível (36+ call sites intocados). Corrige três bugs reais: fade esquerdo permanente no início (o snap-mandatory puxava o scroll antes da detecção rodar), seta "Próximo" desabilitada mesmo com overflow quando os itens chegavam por fetch depois do mount, e dessincronia de defaults entre o hook e o estado de borda. Ganha MutationObserver deferido via requestIdleCallback pra detectar itens adicionados async.
Marquee vertical em modo step por padrão + pausa off-screen — cb024cdc3. 8148ba3ad passa a usar /assets/images/empty.jpg como fallback de imagem de card de jogo e 0ac855ece alinha o comportamento do chevron entre variants de sidebar.
SDK antifraude Incognia integrado e desligado na mesma semana — 0761c3360 adiciona a integração opcional de device fingerprint nos fluxos de login e KYC, habilitável por override em overrides/<brand>/app/config/analytics/incognia.ts, com só a vera-bet-br ligando (enabled: true) e todas as outras marcas sendo no-op ponta a ponta — sem script, sem chamada de SDK, sem icg_token no payload. Quatro dias depois, 0e9ed964f desliga a vera também.
Smartico: identify não-bloqueante e reset ciente de identidade — 6c04ce373. O waitForIdentify deixa de rejeitar num timeout fatal de 15s: em transições visitante→autenticado o SDK pode levar mais de 15s pra fechar o handshake de WebSocket, e o timeout antigo chamava setError(true) em vez de setSdkIdentified(true), deixando a store presa em false pra sempre (reproduzido em /promocode, com o botão de resgate travado em "Carregando..."). Agora faz polling até resolver ou o run ficar stale, com warn suave em 60s e console.error em 120s pra observabilidade em produção. 4ba023e95 desabilita o card e refaz o fetch dos itens da loja depois de STORE_UPDATE, e 4a60674ed refaz o fetch de missões na entrada da página e na visibilidade da aba.
8fdc48cef blinda GlobalCache / LocalStorageCache com import dinâmico e f612213d6 desliga as notificações da topbar por padrão, já cobertas pelo card de baixo.
Pacote @cactus-agents/i18n bumpado pra 0.89.0 — df1053a adiciona o namespace profiles à lista de namespaces de tradução, com as sete locales.
Pacote @cactus-agents/api-client ganha ApiClient.proxyRaw(path, init?) (ad83caf): devolve a Response crua do BFF já com o header set completo aplicado (chave de worker, tenant, origin-domain, version, lang, country, currency, headers de geo, Cookie), sobrescrevendo origin com o tenant — sem isso o BFF rejeitava o origin do iframe do provider de sportsbook com um 404 genérico. É o que destrava o proxy de sportbook do base.
Pacote @cactus-agents/accounts bumpado pra 2.4.0 e depois 2.5.0: 3a742d1 adiciona o campo opcional de identificador estável de conta em AuthUser e 9be11e6 propaga o icg_token do Incognia em login, perfil e status de KYC (LoginPayload, GetUserProfileOptions) — no-op pra quem não passa a opção.
Pacote @cactus-agents/kyc bumpado pra 0.8.0 pelo mesmo 9be11e6 (opção icgToken em getStatus e getStatusRecovery).
7k-bet-br: arte legada de rodapé IBIA + 18+ portada (273013a04); descrição expandida do provider Octoplay pra SEO (cbed72ed8); curadoria de perfis Hulk / Craque Neto / Xand Avião; botão de download do app oculto no FooterStackedPaymentBar (a01a88e33); ~60 redirects de URL legada indexada.
vera-bet-br: pill de saldo de moedas no header do modal de check-in diário (03738a0fc); tile de CTA de mini-games na sidebar espelhando o widget flutuante de campanha (51c40875c); favicon.png legado ao lado do .ico existente (eb8297c9d); apple-touch-icons faltantes na raiz de public/ (900dbf2fe, 9f6c2f8ef, este último matando os 404 de apple-touch-icon-120x120-precomposed.png); único override com Incognia ligado, depois desligado.
cassino-bet-br: rows da categoria /cassino reordenadas e "Jogos Mais Jogados" removida (63e6cc73f); perfil do ratinho refinado.
7k, vera e cassino: botão de download do app oculto no rodapé (84f7cb7b2).
Todos os overrides: proxy /images/ habilitado (3c7971f21); ícone genérico da plataforma removido do conjunto default de ícones PWA (6b63e4623), respeitando a neutralidade de marca no que o cliente final inspeciona.
Sidebar e busca:440237011 suprime o auto-colapso global de /sports quando a flag de keep-labels está ligada; a4c71722e e b40c344cd mantêm apenas a busca de seção na página de providers, sem a busca do header.
Porta do dev server configurável — 0fbe58922 (com ef63485ce no mesmo dia) permite rodar vários workspaces em paralelo sem colisão de porta, e 4b740a0ec (com e9c010a3f) liga strictPort em modo dev pra falhar alto em vez de escorregar pra outra porta silenciosamente.
Novos stages de deploy adicionados à configuração de CI (be0bcf0ab).
front-ops: ambientes de docs e de teste desabilitados (532f658); configurações novas de deploy pra ambientes de dev e stage (539b136); default branch do ng-7k-bet ajustada duas vezes na semana (bf4b846, fc93f8b); auto-deploy habilitado e default branch padronizada em cassino-bet-br e prod-7k-bet-br (0f11c07) e em betpontobet-bet-br (2e0fa0b).
Visão geral do check-in diário atualizada em docs-internal (6cf20c6fb).
O registry de analytics (604b6d3e5) trouxe documentação canônica nova em docs-marketing: a matriz multi-canal de eventos e o contrato de eventos backend-only que o BFF tem que disparar (webhook de Pix pago, detecção de conta dormente, KYC, CRM, CAPI server-side). O plano completo ficou em docs/superpowers/plans/.
A auditoria do módulo do álbum e o documento de migração pro host foram versionados junto com o código na semana seguinte.