Feature: AdSense Ads System (FEAT-41)

Metadata

  • Issue ID: FEAT-41
  • Status: Done
  • Owner: Kzu0-afk
  • Related PRs: 41-implement-ads-system -> dev
  • Canonical feature doc: docs/features/FEAT-41-Implement-Ads-System.md

Overview

Implements a controlled ads layer for StudyBoost using Google AdSense with strict gating rules:

  • system-level gate via FEAT-35 feature flag enable_ads
  • user-level gate via FEAT-39 subscription state (free users only; active paid users excluded)

The integration keeps ads non-intrusive, fails silently when ad loading fails, and avoids security-sensitive routes.


Frontend Behavior

  • Adds a reusable ads module:
    • frontend/components/ads/ads-provider.tsx
    • frontend/components/ads/use-ads-eligibility.ts
    • frontend/components/ads/ad-slot.tsx
  • Loads AdSense script lazily only when all eligibility checks pass.
  • Hides ads while subscription/flag state is loading.
  • Hides ads if:
    • feature flag is disabled/missing
    • user is active paid (pro / plus)
    • AdSense env config is missing
    • provider script fails or is blocked
  • Renders conservative placements on:
    • home discovery page
    • courses index
    • course detail
    • document preview/detail

Backend Behavior

  • Extends FEAT-35 known flag enum with:
    • FeatureFlagName.ENABLE_ADS = 'enable_ads'
  • Reuses existing GET /feature-flags/status/:name endpoint for runtime ads gating.
  • No new Ads-specific endpoint added.
  • No new database table or migration added for v1.

QA Test Scenarios

Scenario IDDescriptionStepsInputExpected Result
FEAT-41-01Free-user ad renderEnable flag, set AdSense env, open approved routeFree userAd slot appears in approved section
FEAT-41-02Paid-user suppressionEnable flag, open approved route as active paid userActive pro / plusNo ad slot and no ad-script usage for rendering
FEAT-41-03Flag off global suppressionDisable enable_ads, open approved routeAny userNo ads rendered
FEAT-41-04Provider blocked/failureBlock AdSense script/ad networkEligible free userUI remains stable; no runtime crash
FEAT-41-05Loading-state suppressionDelay subscription/flag responseEligible routeAds hidden until eligibility is resolved

QA Verification Notes

  • Scoped FEAT-41 backend lint and backend build passed.
  • Scoped FEAT-41 frontend lint passed.
  • QA bug fix: ads now remain hidden until the current route/config pair has its own resolved enable_ads status, preventing stale flag reuse after route/config transitions.
  • Full frontend production build is currently blocked by an upstream prerender dependency unrelated to ads: GET /documents?page=1&limit=3&sort=trending returns 500 from the backend.

Edge Cases

  • Missing flag response defaults to ads disabled.
  • Missing AdSense env vars disable ads safely.
  • Route not in placement allow-list suppresses ads.
  • User plan transitions from free to paid suppress ads after state refresh.
  • Ad blockers prevent script load; ads fail closed without breaking page flow.

Notes

  • Uses existing FEAT-31 auth/session and FEAT-39 subscription data model.
  • Keeps ad rendering policy conservative to preserve study UX.
  • No mock ad data is introduced; slot IDs come from environment configuration.