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 (
freeusers 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.tsxfrontend/components/ads/use-ads-eligibility.tsfrontend/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/:nameendpoint for runtime ads gating. - No new Ads-specific endpoint added.
- No new database table or migration added for v1.
QA Test Scenarios
| Scenario ID | Description | Steps | Input | Expected Result |
|---|---|---|---|---|
| FEAT-41-01 | Free-user ad render | Enable flag, set AdSense env, open approved route | Free user | Ad slot appears in approved section |
| FEAT-41-02 | Paid-user suppression | Enable flag, open approved route as active paid user | Active pro / plus | No ad slot and no ad-script usage for rendering |
| FEAT-41-03 | Flag off global suppression | Disable enable_ads, open approved route | Any user | No ads rendered |
| FEAT-41-04 | Provider blocked/failure | Block AdSense script/ad network | Eligible free user | UI remains stable; no runtime crash |
| FEAT-41-05 | Loading-state suppression | Delay subscription/flag response | Eligible route | Ads 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_adsstatus, 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=trendingreturns500from 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.