posts

Tobi After Shipaton

Tobi didn't place at Shipaton, but I still shipped breaker panel sharing and a way to credit whoever did the work.

Tobi didn’t place at RevenueCat’s Shipaton. I’m not surprised. Growth was still thin, and I didn’t have time to market it while we were buying and moving into our own home and getting a new puppy.

I still shipped Tobi 1.6.0 in October. You can print or share a breaker panel as a PDF, share it as an image, and credit any household member on a task.

Numbers

Last month:

This month:

MRR is slowly increasing, but I’m a LONG way away from being able to pay my bills from my apps. I’m disappointed that my new users are falling so hard, but I’ll start pushing out content for Tobi and Camp Notes soon which should help get those back up.

Experiment Report

Last month, I reported on Tobi’s small September test of the hard paywall against freemium. One person in the freemium group set up a household, and nobody in the hard paywall group did. That wasn’t enough to call a winner, but I rolled freemium out to everyone.

People were still leaving at the name-your-home screen, which I had planned to remove. This November, I shipped a prefilled home name and a new view instead. I’ll watch whether people complete the home-naming step before deciding whether to remove it. If people still drop off, I might remove it altogether.

Technical Spotlight

In JABA, I save the time zone with every date something happened. Otherwise, something at 11 PM on September 30 in Los Angeles shows up as October 1 when you open the app in Tokyo. Here’s what it looks like.

import Foundation

struct DatedEvent {
    let occurredAt: Date
    let occurredTimeZone: String

    func occurredDay() -> (year: Int, month: Int, day: Int)? {
        guard let zone = TimeZone(identifier: occurredTimeZone) else { return nil }
        let date = Calendar(identifier: .gregorian)
            .dateComponents(in: zone, from: occurredAt)
        guard let year = date.year, let month = date.month, let day = date.day else {
            return nil
        }
        return (year, month, day)
    }
}

occurredAt is the exact moment it happened, and occurredTimeZone is where it happened. occurredDay() reads the calendar date in that saved zone instead of the device’s current one, so the date doesn’t change after you travel. If the saved zone isn’t valid, it returns nil instead of guessing.

This Month’s Focus

In November, I am focused on the following:

open to work

I'm looking for my next thing, whether that's full-time, consulting, or a long-term contract. If you're building something where the details matter, hire me on Toptal.

Oct 2, 2026 shipping notes
Tobi Goes to Shipaton
I submitted Tobi to RevenueCat's Shipaton.
May 1, 2026 shipping notes
From Screenshots to Downloads
Screenshot changes improved Camp Notes discoverability and pushed better growth this month, while onboarding and paywall tests still need more signal.
Apr 4, 2026 shipping notes
Numbers Up, Lessons Mixed 📊
New users nearly 5x'd in Camp Notes, plus a technical trick to keep everyone on the latest version.