116Lvelevensix
[ LOG 001 ] Studio · 2 min read
Kt001 · KTL

Why you can’t level yourself up

KTL has one rule the rest of the app is built around. Here’s where it came from and what it costs us.

Every app has a number it wants to go up. In most of them you can push it yourself: post more, log in more, tap more. We built KTL the other way round, and almost every decision since has been downstream of that one choice.

The rule

In KTL you can’t earn your own XP. The only way to level up is for your partner to boost you, and the only way they level up is for you to boost them. You get six boosts a day, and none of them send without a reason.

  • 0 XP you can give yourself
  • 6 boosts a day
  • 2 people per world

Where it came from

The first prototype had a daily streak, like everything else. It worked, in the sense that people opened it. But the streak was about the app, not about the person on the other end. When we took it out and made the only reward something your partner hands you, the tone of the whole thing changed overnight. You can read the current version at getktl.app; under the hood it’s still one function, boost(reason).

Anyone can type good morning. A boost costs one of your six.

From getktl.app

What it costs us

A rule like this isn’t free. We knew some of the costs going in and found the rest in testing:

  1. Nobody can grind on their own, so a quiet day for one of you is a quiet day for both.
  2. The reason field slows sending down on purpose, which shows up as a drop-off we’ve chosen not to fix.
  3. We have to explain the rule on the first screen, every time.

Observation: couples who wrote longer reasons kept using the app longer. Not more boosts, longer ones.

Kaptr, our other app, takes the opposite view: everyone at an event shoots freely and the gallery fills itself. Different problem, different rule.

Under the hood

The rule is enforced on the server, not the phone, so there’s no clever way round it. A few details that took longer than they should have:

  • Timestamps are stored in UTC.
  • Each day’s six boosts reset on the sender’s local midnight.
  • The server decides who levelled whom.

The check itself is two lines:

// server/boost.ts
  export async function boost(from, to, reason) {
    if (!reason.trim()) throw new Error('A boost needs a reason')
    if (from === to) throw new Error('You can’t boost yourself')
  }

What’s next

Next up is a weekly recap: the reasons you sent each other, read back on a Sunday. No new numbers, just the words. If that sounds like your kind of app, join the waitlist.

One email when something new ships.