Inspiration

Every social app I use is the same shape: an infinite column, everybody. talking at once, and a ranking system quietly deciding who gets heard. I wanted to know what happens if you delete all of it and leave exactly one slot.

Not one slot per person. One slot. For everyone.

Before I wrote a line of code, I ran it by hand. I put seven people in a WhatsApp group with one rule: whoever sends a message owns the screen, and Anyone can take it by sending their own. I expected it to die in five minutes. Over 28 minutes the screen changed hands 58 times. People stopped. talking to each other and started talking at the screen. Someone typed. "TAKE" and it turned into a verb the group used for the rest of the night.

That group chat is the entire product. Everything since has been an attempt. to not ruin it.

What it does

ONE is a single live screen shared by everybody who has the app.

One person owns it. Their message is the only thing on it. There is no feed. to scroll, no algorithm, no follower count—the whole app is one screen and the name of whoever is currently holding it.

Anyone can take it. You write your message, you take the screen, and their Words are gone. Not pushed down a feed—gone. Every phone running ONE changes at the same moment.

Then you find out what it feels like from the other side, because somebody takes it off you, and your phone tells you who.

Taking the screen never costs money. It costs waiting. You hold a balance of takes that refills on a ladder—60 seconds, then 3 minutes, 8, 20, 30— Getting slower the more you spam, resetting once you leave it alone. Credits and rewarded ads only skip the wait. There is deliberately no way to buy the screen itself, because the moment the loudest wallet wins, the game is over.

How I built it

Kotlin and Jetpack Compose on the client, Supabase and Postgres on the back. I'm one person, so almost every decision was "What can I make correct and still finish."

The interesting part is that "one screen" isn't a UI claim—it's a concurrency guarantee, and it lives in the database, not the app.

A takeover runs inside a Postgres function that takes an advisory. transaction lock, re-reads the current reign`, and rejects the Write withSTALE_REIGNif the screen moved while the request was in flight. A partial unique index enforces the invariant that the app is named After: at most one reign row can haveended_at is null`. Not "the app prevents "it"—the database physically cannot store two live screens. Every take carries a client-generated request ID, so a retry on a flaky train The connection replays the same result instead of stealing the screen twice.

Around that:

  • RevenueCat runs the economy—ONE Credits as virtual currency, AdMob rewarded ads verified server-side through SSV, so a watched ad grants a cooldown skip the client cannot mint on its own, and a Stripe web checkout through Funnels for buying outside the store.
  • OneSignal runs the part that brings you back. Losing the screen fires a push naming who took it, and a journey waits an hour and follows up if You haven't retaken it. Notification permission is only ever requested. after your first win, when the app has actually earned the right to ask.
  • Moderation runs before anything is published, in three layers: an on-device filter, an automated classifier with thresholds for auto-reject and auto-approve, and a human queue for everything in between. The first Message from any new account always goes to the queue. Reports, blocks, a A ban ladder and a kill switch sit behind it.

Challenges I ran into

The first economy was wrong, and I only found out because I tested it on humans first. My original plan was pay-per-takeover. The WhatsApp night killed it: the fun was in the back-and-forth, and a price tag on every turn would have stopped it dead. Money now buys impatience, never the screen.

Two people taking at the same instant. The obvious implementation— read, then write—silently loses one of them and can leave two live reigns in the table. Getting to a version I could actually prove correct meant moving the whole operation into the database and letting Postgres enforce the invariant rather than trusting application code to be careful.

I found my own security hole. My server originally accepted the reward. type from the client, which meant a modified app could claim it had watched an ad that never played. I rewrote it so the grant only exists if RevenueCat confirms the ad server-side. It was working, and it was wrong, and those are the bugs that ship.

One screen means one bad message reaches everyone. There's no "your feed." is different from mine" to hide behind, and no volume of moderators to throw at it. That's why nothing publishes before it's checked, and why the first A message from a brand-new account is always seen by a person.

Anonymous sessions died on app update. Testers updated and lost their handle and their history. It hit every tester; it looked like a small storage bug, and it broke the one thing the app runs on—the grudge.

Accomplishments that I'm proud of

Shipping it. Solo, through Google Play's closed-testing gate, to production.

The takeover is provably atomic rather than probably fine—I can point at the index that makes a second live screen unrepresentable.

The economy monetizes without letting money win. Ads and credits buy you back into the game sooner; they never buy you the screen. That constraint cost me revenue, and it's the reason the game is still worth playing.

Moderation that happens before publication rather than apologizing after it.

And a product decision I can trace to a specific conversation with a specific person on a specific night, rather than a guess.

What I learned

Test the mechanic before you build the app. Seven people and a group chat told me more in 28 minutes than a month of building would have.

Never trust the client with anything that has value. If the phone can claim it, someone will claim it.

Building in public isn't marketing—it's a bug report you don't have to pay for. Several of the things above exist because somebody told me about the app. was confusing, and I believed them instead of defending it.

And the hardest engineering in a social app isn't scale. It's the invariant. You promised in the name.

What's next for ONE

Private ONEs—one screen for your group chat, your class, your team, with the same single-slot rule.

Challenge links that open straight onto the current owner, so taking the Screening off a friend is one tap from a message.

Richer messages than text, without losing the thing that makes it work: that Only one of them exists at a time.

Built With

Share this project:

Updates

Submission history