- Add per-slot lazy loading with Intersection Observer API - Implement fill detection via MutationObserver and data-ad-status - Add configurable rootMargin and fillTimeout from database - Generate dynamic CSS based on lazy_loading_enabled setting - Add legacy mode fallback for browsers without IO support - Include backup of previous implementation (adsense-loader.legacy.js) - Add OpenSpec documentation with test plan (72 tests verified) Schema changes: - Add lazy_loading_enabled (boolean, default: true) - Add lazy_rootmargin (select: 0-500px, default: 200) - Add lazy_fill_timeout (select: 3000-10000ms, default: 5000) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
13 KiB
Especificacion: AdSense Lazy Loading
Purpose
Define el comportamiento del sistema de carga diferida de anuncios AdSense usando Intersection Observer para cargar ads individualmente cuando entran al viewport, ocultando slots que no reciben contenido.
ADDED Requirements
Requirement: Carga Individual por Visibilidad
The system MUST load each AdSense ad slot individually when it enters the viewport, NOT all at once.
Scenario: Slot entra al viewport por primera vez
- WHEN un elemento
.roi-ad-slot[data-ad-lazy="true"]entra al viewport (considerando rootMargin) - THEN el sistema DEBE ejecutar
adsbygoogle.push({})SOLO para ese slot - AND el sistema DEBE marcar el slot como "activado" para no procesarlo de nuevo
- AND el sistema DEBE observar el
<ins>interno para detectar contenido
Scenario: Multiples slots en viewport inicial
- GIVEN la pagina tiene 3 slots visibles en el viewport inicial
- WHEN la pagina termina de cargar
- THEN el sistema DEBE activar los 3 slots en orden DOM (sin delay entre ellos)
- AND la activacion es sincrona: push() → siguiente push() inmediatamente
- AND el sistema NO DEBE activar slots que estan fuera del viewport
Clarificacion: "Secuencial" significa en orden DOM, uno tras otro sin delay artificial. NO hay setTimeout entre activaciones. El Intersection Observer dispara callbacks para todos los elementos visibles en el mismo frame.
Scenario: Usuario hace scroll rapido
- GIVEN el usuario hace scroll rapido pasando varios slots
- WHEN los slots entran y salen del viewport rapidamente
- THEN el sistema DEBE activar cada slot que entre al viewport
- AND el sistema NO DEBE cancelar la activacion si el slot sale del viewport
Requirement: Biblioteca Cargada Una Sola Vez
The system MUST load the adsbygoogle.js library only once, when the first slot is activated.
Scenario: Primer slot activado
- GIVEN la biblioteca
adsbygoogle.jsNO ha sido cargada - WHEN el primer slot entra al viewport
- THEN el sistema DEBE cargar la biblioteca
- AND el sistema DEBE esperar a que la biblioteca cargue (onload callback)
- AND ENTONCES ejecutar el push para ese slot
Scenario: Slots subsecuentes
- GIVEN la biblioteca
adsbygoogle.jsYA fue cargada - WHEN otro slot entra al viewport
- THEN el sistema DEBE ejecutar el push inmediatamente
- AND el sistema NO DEBE intentar cargar la biblioteca de nuevo
Requirement: Slots Ocultos por Defecto
The system MUST hide ad slots by default and show them only when they have content.
Scenario: Slot en estado inicial
- WHEN la pagina renderiza un
.roi-ad-slot[data-ad-lazy="true"] - THEN el slot DEBE tener
display: nonevia CSS dinamico - AND el slot NO DEBE ocupar espacio en el layout
Scenario: Slot recibe contenido de Google
- GIVEN un slot fue activado con push()
- WHEN Google inyecta contenido dentro del
<ins class="adsbygoogle"> - THEN el sistema DEBE agregar clase
roi-ad-filledal slot - AND el slot DEBE hacerse visible (
display: block)
Scenario: Slot NO recibe contenido (timeout)
- GIVEN un slot fue activado con push()
- WHEN pasa el tiempo configurado en
lazy_fill_timeoutsin que Google inyecte contenido - THEN el sistema DEBE agregar clase
roi-ad-emptyal slot - AND el slot DEBE permanecer oculto
- AND el sistema DEBE dejar de observar ese slot
Requirement: Pre-carga con rootMargin
The system MUST pre-load ads before they enter the visible viewport to ensure smooth UX.
Scenario: Configuracion de rootMargin
- WHEN se inicializa el Intersection Observer
- THEN DEBE usar el valor de
lazy_rootmargindesde configuracion - AND el formato DEBE ser
'{value}px 0px'
Scenario: Slot dentro del rootMargin
- GIVEN un slot esta 150px debajo del viewport visible
- AND
lazy_rootmargines 200 - WHEN el Intersection Observer evalua visibilidad
- THEN el slot DEBE considerarse "visible" y activarse
Requirement: Deteccion de Contenido con Criterios Concretos
The system MUST use specific criteria to determine when an ad slot has been filled.
Scenario: Google agrega atributo data-ad-status="filled"
- GIVEN un slot fue activado
- WHEN Google agrega
data-ad-status="filled"al<ins> - THEN el sistema DEBE marcar inmediatamente como
roi-ad-filled - AND el sistema DEBE desconectar observadores de ese slot
Scenario: Google agrega atributo data-ad-status="unfilled"
- GIVEN un slot fue activado
- WHEN Google agrega
data-ad-status="unfilled"al<ins> - THEN el sistema DEBE marcar inmediatamente como
roi-ad-empty - AND el sistema DEBE desconectar observadores de ese slot
Scenario: Fallback - Google inyecta iframe sin atributo
- GIVEN un slot fue activado
- AND el
<ins>NO tiene atributodata-ad-status - WHEN Google agrega un
<iframe>dentro del<ins> - THEN el sistema DEBE marcar como
roi-ad-filled
Scenario: Fallback - Google agrega div con id
- GIVEN un slot fue activado
- AND el
<ins>NO tiene atributodata-ad-status - WHEN Google agrega un
<div id="...">dentro del<ins> - THEN el sistema DEBE marcar como
roi-ad-filled
Scenario: Limpieza de observadores
- GIVEN un slot fue marcado como
roi-ad-filledoroi-ad-empty - WHEN el estado final es determinado
- THEN el sistema DEBE desconectar el MutationObserver de ese slot
- AND el sistema DEBE desconectar el IntersectionObserver de ese slot
Requirement: Manejo de Errores de Red
The system MUST handle network errors when loading the AdSense library.
Scenario: Error de carga de biblioteca - primer intento
- GIVEN el sistema intenta cargar
adsbygoogle.js - WHEN la carga falla (onerror)
- THEN el sistema DEBE esperar 2 segundos
- AND el sistema DEBE reintentar la carga UNA vez
Scenario: Error de carga de biblioteca - segundo intento fallido
- GIVEN el primer intento de carga fallo
- AND el segundo intento tambien falla
- WHEN el onerror se dispara por segunda vez
- THEN el sistema DEBE marcar TODOS los slots como
roi-ad-error - AND el sistema DEBE registrar error en consola si debug habilitado
- AND el sistema NO DEBE intentar mas recargas
Scenario: Slots permanecen ocultos tras error
- GIVEN la biblioteca fallo en cargar
- WHEN los slots tienen clase
roi-ad-error - THEN los slots DEBEN permanecer ocultos
- AND NO DEBEN mostrar espacios vacios en la pagina
Requirement: Fallback para Navegadores Sin Soporte
The system MUST provide fallback for browsers without Intersection Observer support.
Scenario: Navegador sin Intersection Observer
- GIVEN
window.IntersectionObserveres undefined - WHEN el script se inicializa
- THEN el sistema DEBE usar el modo legacy (cargar todos despues de interaccion/timeout)
- AND el sistema DEBE registrar un mensaje de debug indicando fallback
Scenario: Navegador con soporte parcial
- GIVEN el navegador soporta Intersection Observer pero no MutationObserver
- WHEN el script se inicializa
- THEN el sistema DEBE usar Intersection Observer para activacion
- AND el sistema DEBE usar timeout fijo para determinar fill (sin deteccion dinamica)
Requirement: Compatibilidad con Ads Dinamicos
The system MUST support ads injected dynamically after page load.
Scenario: Contenido cargado via AJAX
- GIVEN la pagina carga contenido adicional via AJAX con nuevos slots
- WHEN el evento
roi-adsense-activatees disparado - THEN el sistema DEBE buscar nuevos slots
.roi-ad-slot[data-ad-lazy="true"]no observados - AND el sistema DEBE agregarlos al Intersection Observer
Scenario: Infinite scroll
- GIVEN la pagina implementa infinite scroll
- WHEN nuevos slots son agregados al DOM
- THEN el sistema DEBE detectarlos automaticamente (MutationObserver en body)
- OR esperar evento
roi-adsense-activatepara procesarlos
Requirement: Configuracion desde Base de Datos
The system MUST read configuration from database via wp_localize_script, NOT from hardcoded values.
Scenario: Configuracion disponible en JS
- WHEN el script
adsense-loader.jsse ejecuta - THEN DEBE leer configuracion de
window.roiAdsenseConfig - AND los valores DEBEN incluir:
lazyEnabled(boolean) - desde campolazy_loading_enabledrootMargin(string) - desde campolazy_rootmargin+ 'px 0px'fillTimeout(number) - desde campolazy_fill_timeoutdebug(boolean) - desde WP_DEBUG
Scenario: Modo lazy deshabilitado
- GIVEN
roiAdsenseConfig.lazyEnabledes false - WHEN el script se inicializa
- THEN el sistema DEBE usar el modo legacy (cargar todos al inicio)
- AND los slots DEBEN ser visibles por defecto (sin display:none)
Requirement: No Manipular Ads Cargados
The system MUST NOT remove, recycle, or manipulate ads after they are loaded.
Scenario: Usuario scrollea pasando un ad
- GIVEN un ad fue cargado y mostrado
- WHEN el usuario scrollea y el ad sale del viewport
- THEN el sistema NO DEBE remover el ad del DOM
- AND el sistema NO DEBE ocultar el ad
- AND el sistema NO DEBE intentar "reciclar" el slot
Scenario: Ad permanece en pagina
- GIVEN un ad fue cargado exitosamente
- WHEN la sesion del usuario continua
- THEN el ad DEBE permanecer en su posicion original
- AND el ad DEBE mantener su contenido intacto
Requirement: Logging de Debug Condicional
The system MUST provide debug logging only when enabled via WP_DEBUG.
Scenario: Debug habilitado
- GIVEN
roiAdsenseConfig.debuges true - WHEN ocurre cualquier evento significativo
- THEN el sistema DEBE registrar en console.log con prefijo
[AdSense Lazy] - AND los eventos incluyen: inicializacion, activacion de slot, deteccion de fill, timeout, error
Scenario: Debug deshabilitado
- GIVEN
roiAdsenseConfig.debuges false - WHEN el script ejecuta
- THEN el sistema NO DEBE generar output en consola
Requirement: CSS Generado Dinamicamente
The system MUST generate CSS via CSSGeneratorService, NOT static CSS files.
Scenario: Lazy loading habilitado
- GIVEN
lazy_loading_enabledes true en BD - WHEN
AdsensePlacementRenderergenera output - THEN DEBE usar
CSSGeneratorServicepara generar:.roi-ad-slot { display: none }.roi-ad-slot.roi-ad-filled { display: block }.roi-ad-slot.roi-ad-empty { display: none }
Scenario: Lazy loading deshabilitado
- GIVEN
lazy_loading_enabledes false en BD - WHEN
AdsensePlacementRenderergenera output - THEN NO DEBE agregar
display: nonea.roi-ad-slot - AND los slots DEBEN ser visibles por defecto
Requirement: Integracion con Schema JSON
The system MUST store lazy loading configuration in the existing adsense-placement.json schema.
Scenario: Campos en grupo behavior
- WHEN el schema
adsense-placement.jsones leido - THEN el grupo
behaviorDEBE contener:lazy_loading_enabled(boolean, default: true)lazy_rootmargin(select, default: "200")lazy_fill_timeout(select, default: "5000")
Scenario: Sincronizacion a BD
- WHEN se ejecuta
wp roi-theme sync-component adsense-placement - THEN los campos de lazy loading DEBEN crearse en BD
- AND los valores default DEBEN aplicarse si no existen
Requirement: Accesibilidad de Slots Ocultos
The system MUST ensure hidden ad slots do not interfere with assistive technologies.
Scenario: Slot oculto no interfiere con lectores de pantalla
- GIVEN un slot tiene
display: none(estado inicial o roi-ad-empty) - WHEN un lector de pantalla procesa la pagina
- THEN el slot NO DEBE ser anunciado ni navegable
- AND el contenido oculto NO DEBE aparecer en el arbol de accesibilidad
Scenario: Slot visible es accesible
- GIVEN un slot fue marcado como
roi-ad-filled - WHEN el slot se hace visible (
display: block) - THEN el contenido del ad DEBE ser accesible para lectores de pantalla
- AND el iframe de Google conserva su propia accesibilidad
Nota tecnica: display: none automaticamente remueve elementos del arbol de accesibilidad. No se requiere aria-hidden adicional.
Requirement: Interaccion con Cache
The system MUST document cache implications when lazy loading settings change.
Scenario: Cambio de configuracion requiere cache flush
- GIVEN
lazy_loading_enabledcambia de true a false (o viceversa) - WHEN el administrador guarda la configuracion
- THEN el FormBuilder DEBE mostrar aviso de que se requiere vaciar cache
- AND el CSS dinamico cambiara en el proximo render sin cache
Scenario: Usuario con cache obsoleto
- GIVEN un usuario tiene HTML cacheado con
display: noneen slots - AND el admin deshabilito lazy loading
- WHEN el usuario visita la pagina
- THEN los slots permaneceran ocultos hasta que el cache expire
- AND esto es comportamiento esperado (no es un bug)