PageToll: a social media blocker that makes you read a page by Haris NabeelPageToll: a social media blocker that makes you read a page by Haris Nabeel

PageToll: a social media blocker that makes you read a page

Haris Nabeel

Haris Nabeel

The problem

People open social apps dozens of times a day without deciding to. Hard blockers get disabled within a week, and soft friction like timers doesn't change the reflex.
PageToll replaces the reflex with a small, deliberate act. Open a gated app, read one page of a book you chose, and answer one comprehension question. Then choose: 30 minutes in the app, or keep reading. Both buttons look the same on purpose. Over a few months, users finish real books.

What I built

I built PageToll end to end at Supportive, Inc.: native iOS and Android apps, the backend, and the infrastructure behind subscriptions and content.
Two native apps: Swift and SwiftUI on iOS, Kotlin and Jetpack Compose on Android.
A Next.js backend on Vercel, acting as a resource server over Supabase Auth, with Prisma and Postgres.
Subscriptions across the App Store and Google Play through Adapty, with our own database as the source of truth.
A content pipeline that turns public-domain books into 200-word pages with AI-generated comprehension questions.
Background jobs on Trigger.dev, an admin panel, and transactional email.

Three hard problems

1. Blocking apps on two operating systems

Android has no blocking API, and apps that use AccessibilityService for this get pulled from Google Play. I used a foreground service that checks the foreground app every 400ms and opens the gate over it. Unlock timers survive process death and reboots.
iOS allows blocking through Screen Time, with tight limits. The app is five targets sharing an App Group, and app identities are opaque tokens. The system shield can't open the main app, so the shield extension writes a flag and posts a notification. One tap takes the user straight into the gate.

2. Subscriptions, and the incident that tested the design

Purchases, renewals, and refunds arrive as Adapty webhooks and are applied idempotently. Every six hours a job compares each user against Adapty and corrects drift. If every lookup fails, the job fails loudly instead of reporting a green run.
In September, that job started marking every iOS subscriber as expired. iOS sent user IDs in uppercase, Android in lowercase, and Adapty matches IDs case-sensitively. The same day, I fixed both platforms, made the job merge split profiles and never revoke access on a missing profile, and restored every affected user.
The lesson: a job that can revoke access needs a much higher bar of evidence than one that can grant it.

3. Turning public-domain books into a reading product

An ingestion script strips Project Gutenberg boilerplate, uses an LLM to find where the real text starts, splits each book into 200-word pages at paragraph boundaries, and generates one question per page. Everything is prepared ahead of time, so serving a page is a single database read.

Architecture

Two native apps talk to a Next.js API with Supabase JWTs. Adapty validates store purchases and sends webhooks. Postgres is the source of truth, and Trigger.dev runs the reconcile job.

Outcomes

Live on the App Store, version 1.1, after working through two App Review rejections.
213 commits across iOS, Android, backend, and web.
A production incident found, fixed on both platforms, and fully remediated in one day.
Like this project

Posted Sep 26, 2026

Native iOS and Android apps plus backend, built end to end. Cross-store subscriptions, Screen Time blocking, and a content pipeline. Live on the App Store.