<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Richard Andrews</title>
    <description>The latest articles on DEV Community by Richard Andrews (@the_unmanaged_boy).</description>
    <link>https://dev.to/the_unmanaged_boy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3832547%2Fe83a71ba-0f4a-4c05-9504-c8010a6adc0d.jpg</url>
      <title>DEV Community: Richard Andrews</title>
      <link>https://dev.to/the_unmanaged_boy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/the_unmanaged_boy"/>
    <language>en</language>
    <item>
      <title>7,920 Minutes/Month Wasted on My Phone. Now What?</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Mon, 23 Mar 2026 14:34:38 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/7920-minutesmonth-wasted-on-my-phone-now-what-2n93</link>
      <guid>https://dev.to/the_unmanaged_boy/7920-minutesmonth-wasted-on-my-phone-now-what-2n93</guid>
      <description>&lt;p&gt;Last year I opened my iPhone's Screen Time report and stared at it for a long time. 78 hours of YouTube. 43 hours of JioHotstar. 11 hours of Instagram. In a single month. That is 132 hours — more than &lt;strong&gt;five full days&lt;/strong&gt; — spent watching other people live their lives while I put mine on pause.&lt;/p&gt;

&lt;p&gt;If you are reading this, you probably know the feeling. The guilt that hits every night when you realize the day is gone and you have done nothing meaningful. The gap between who you want to be and what your Screen Time report says you actually are. That gap was eating me alive.&lt;/p&gt;

&lt;p&gt;This is the story of how I went from wasting 132 hours a month on my phone to completing 85% of my daily habits, building a 14-day streak, and finally breaking the shame cycle that had controlled my life for years. The solution was not willpower. It was not discipline. It was one simple principle that anyone can apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  132 Hours Gone: The Wake-Up Call
&lt;/h2&gt;

&lt;p&gt;I am not going to sugarcoat this. I was addicted to my phone. Not in the casual "I scroll a bit before bed" way. In the "I wake up, immediately open YouTube, and suddenly it is 11 AM" way. The numbers do not lie:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;78 hours on YouTube.&lt;/strong&gt; That is nearly 2.5 hours every single day, watching videos I would not remember an hour later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;43 hours on JioHotstar.&lt;/strong&gt; Entire seasons of shows consumed in weekend binges that left me feeling empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;11 hours on Instagram.&lt;/strong&gt; Reels, stories, explore page, repeat. The same loop, hundreds of times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I tried Apple's Screen Time limits. You know what happened? I tapped "Ignore Limit" within seconds. Every single time. It is the most useless button in iOS — a speed bump on a highway. I tried setting a passcode I would not remember. I memorized it within a day. I tried deleting apps. I reinstalled them within hours.&lt;/p&gt;

&lt;p&gt;I tried willpower. I told myself I would not open YouTube until after lunch. I lasted two days. I tried app timers. I swiped past every notification. I tried the "put your phone in another room" trick. I just walked to the other room. Every strategy assumed I had discipline. I did not. That was the entire problem.&lt;/p&gt;

&lt;p&gt;The worst part was the shame spiral. You waste three hours scrolling, then you feel guilty about wasting three hours, and then the guilt makes you feel terrible, and then you scroll more to escape the feeling of feeling terrible. It is a loop, and nothing I tried could break it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Principle That Changed Everything
&lt;/h2&gt;

&lt;p&gt;The idea came to me on a random Tuesday afternoon. I was staring at my phone, Instagram open, knowing I should be reading the book that had been sitting on my nightstand for three weeks. And a thought hit me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my phone would not let me open Instagram until I had read for 30 minutes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a reminder. Not a timer. Not a popup I could dismiss. An actual lock. A real barrier between me and the app — and the only key is doing the thing I actually want to do with my life.&lt;/p&gt;

&lt;p&gt;That was the insight that changed everything: &lt;strong&gt;willpower does not work, but barriers do.&lt;/strong&gt; You do not eat junk food if there is no junk food in the house. You do not skip the gym if your friend is waiting for you there. The most effective behavior change systems do not rely on motivation. They change the environment so the desired behavior is the path of least resistance.&lt;/p&gt;

&lt;p&gt;I wanted to apply that same principle to screen time. Instead of trying to restrict phone use (which feels like punishment), I wanted to flip the relationship entirely. &lt;strong&gt;Earn your screen time.&lt;/strong&gt; Do the habits you care about, and your apps unlock as a reward. The screen time is not the enemy. It is the incentive.&lt;/p&gt;

&lt;p&gt;I searched the App Store for something like this. I found app blockers that work on timers. I found friction-based tools that add a pause before opening apps. I found habit trackers with streaks and reminders. But nothing that combined all three into a single system: &lt;strong&gt;lock the apps, tie the unlock to habits, and make the lock unbypassable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I decided to build it myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Idea to App Store in Under 3 Months
&lt;/h2&gt;

&lt;p&gt;Some context: I am a product manager by day, but I have software development experience from earlier in my career. I had built side projects before, including a few iOS apps that shipped to the App Store but never gained traction.&lt;/p&gt;

&lt;p&gt;I started building in late 2025. My goal was simple: get a working app into the App Store as fast as possible, validate whether the concept actually works, and iterate from there. No elaborate roadmap. No six-month timeline. Just build the core loop and ship it.&lt;/p&gt;

&lt;p&gt;The core loop was straightforward: lock apps, set habits, check off habits, unlock apps. But the &lt;strong&gt;Screen Time API&lt;/strong&gt; nearly killed the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Screen Time API Fight
&lt;/h3&gt;

&lt;p&gt;Apple's Screen Time API (FamilyControls framework) is the only way to enforce real app locks on iOS without jailbreaking. It is also one of the most poorly documented APIs Apple has ever shipped. The official docs are sparse. Stack Overflow threads are full of developers hitting the same walls. The error messages are cryptic.&lt;/p&gt;

&lt;p&gt;Here is what made it hard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authorization is fragile.&lt;/strong&gt; The FamilyControls authorization can silently fail, revoke itself, or behave differently between iOS versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apple's review process is strict.&lt;/strong&gt; Apps using the Screen Time API get extra scrutiny during App Store review. My first submission was rejected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited community knowledge.&lt;/strong&gt; Because so few apps use this API, there is almost no community support. Most problems I hit, I had to solve from scratch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the upside of building on the Screen Time API is massive: &lt;strong&gt;the lock is real.&lt;/strong&gt; It is enforced at the operating system level. You cannot bypass it by force-quitting the app, restarting your phone, or switching accounts. When Habit Doom locks Instagram, Instagram is locked. Period.&lt;/p&gt;

&lt;p&gt;From first line of code to App Store approval, the entire process took under three months. I built it solo, mostly during nights and weekends. And yes, one of my daily habits in the app was "Build Habit Doom." More on that later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Gamification Beats Willpower
&lt;/h2&gt;

&lt;p&gt;Here is a truth most self-improvement advice ignores: &lt;strong&gt;you cannot hate yourself into better habits.&lt;/strong&gt; Restriction without reward does not work. If the system feels like punishment, you will abandon it. The key is making good behavior feel like winning, not suffering.&lt;/p&gt;

&lt;p&gt;I spent a lot of time studying what actually makes people stick with behavior change. The answer was not discipline. It was gamification — but the right kind. Not fake badges and confetti. Real rewards tied to real desires.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaks and Loss Aversion
&lt;/h3&gt;

&lt;p&gt;Habit Doom tracks two types of streaks: individual habit streaks and Perfect Day streaks (where you complete every habit on your list). Streaks work because of loss aversion — the psychological principle that losing something hurts roughly twice as much as gaining it feels good. Losing a 14-day streak hurts more than gaining day 15 feels rewarding. That asymmetry is the engine.&lt;/p&gt;

&lt;p&gt;But in Habit Doom, the streak is backed by a real consequence: if you break it, your apps are still locked tomorrow and you have to start from scratch. The streak is not just a number. It is tied to your daily experience of using your phone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Earned Screen Time
&lt;/h3&gt;

&lt;p&gt;Every time you complete your habits and unlock your apps, Habit Doom tracks how much screen time you have earned. You can see a running total: "Time Earned: 1:01:50." This reframes your relationship with screen time entirely. Instead of feeling guilty about scrolling, you see it as something you legitimately earned by doing the work first. Screen time stops being a vice and starts being a reward.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lock Screen
&lt;/h3&gt;

&lt;p&gt;When your apps are locked, Habit Doom displays them with padlock icons. The lock screen creates urgency. You see the apps you want, you see the padlocks, and you feel motivated to go do your habits so you can unlock them. It is motivation through visibility, not punishment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results: 10% to 85% in 4 Weeks
&lt;/h2&gt;

&lt;p&gt;I tracked everything from day one. Here are the results after four weeks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Habit completion rate: 10% to 85%.&lt;/strong&gt; Before Habit Doom, I completed maybe one out of ten habits I set for myself. After four weeks, I was hitting 85% consistently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Longest daily streak: 14 days.&lt;/strong&gt; The longest streak I have ever maintained on any habit system, ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;138 total check-ins.&lt;/strong&gt; Across all my habits, 138 individual completions in four weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;35 hours 30 minutes of earned screen time.&lt;/strong&gt; Every minute of it was guilt-free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what I was tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read.&lt;/strong&gt; 11-day streak, my most consistent habit. The lock mechanic turned "I should read more" into "I read every day."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guitar Practice.&lt;/strong&gt; 23 check-ins. I went from picking up my guitar once a month to playing almost daily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drink Water.&lt;/strong&gt; 79 check-ins. The simplest habit on my list, but the one that made me feel the best physically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build Habit Doom.&lt;/strong&gt; 20 check-ins. Yes, one of my habits was literally building the app. I shipped a complete iOS app in under three months while working a full-time job, largely because I could not negotiate my way out of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The biggest change was not in the numbers. It was in how my days felt. Before Habit Doom, I would wake up, scroll for an hour, feel guilty, and then try to be productive through a fog of shame. After Habit Doom, I wake up, do my habits (because my apps are locked anyway), and then open my phone feeling like I earned it. The guilt is gone. The shame spiral is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 Lessons That Apply to Any Habit
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Stop relying on reminders. Start building barriers.
&lt;/h3&gt;

&lt;p&gt;Reminders assume you have willpower in the moment. You do not. Barriers are different. A barrier does not ask you to make a choice. It removes the bad option entirely. &lt;strong&gt;If you want to change your behavior, stop trying to motivate yourself and start redesigning your environment.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. You do not need to quit the things you love. You need to earn them.
&lt;/h3&gt;

&lt;p&gt;The goal was never to stop using social media. It was to stop using it &lt;em&gt;compulsively&lt;/em&gt;. The difference between scrolling with guilt and scrolling after you have earned it is the difference between a vice and a reward.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real rewards beat fake rewards. Every time.
&lt;/h3&gt;

&lt;p&gt;Badges and confetti feel good for a week, then you stop caring. &lt;strong&gt;The best motivation is not artificial. It is connecting the habit you need to build with the reward you already desire.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Accountability works best when you cannot negotiate with it.
&lt;/h3&gt;

&lt;p&gt;One of my daily habits was "Build Habit Doom." The app was holding me accountable to building the app. I shipped a complete iOS app in under three months while working a full-time job, largely because I could not negotiate my way out of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho3eocwts6e2a35tta19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fho3eocwts6e2a35tta19.png" alt="Habit Doom UI" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I built &lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; to be that system. It is live on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;, free to download. Whether you use my app or build your own barriers, the principle is the same: &lt;strong&gt;stop trying harder and start making the bad choice impossible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My current read streak is 11 days and counting. The shame spiral is gone. The 78-hour YouTube months are behind me.&lt;/p&gt;

&lt;p&gt;You already know what you need to do. The only question is whether you will keep scrolling or finally do something about it.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>productivity</category>
      <category>ios</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>97 Downloads. $0 Revenue.</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Mon, 23 Mar 2026 14:15:08 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/97-downloads-0-revenue-4dgh</link>
      <guid>https://dev.to/the_unmanaged_boy/97-downloads-0-revenue-4dgh</guid>
      <description>&lt;p&gt;I am going to share every number. No spin. No "we are seeing great traction" corporate speak. Just the raw data from Habit Doom's first month on the App Store.&lt;/p&gt;

&lt;p&gt;Habit Doom is an iOS app I built solo that locks your distracting apps (Instagram, TikTok, YouTube) until you complete your daily habits. No timer. No gentle reminder. A real lock at the operating system level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F448pkhws7ai2wgc4dmm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F448pkhws7ai2wgc4dmm7.png" alt="app ui screenshots" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is either inspiring transparency or embarrassing public self-exposure, depending on how you look at it. Either way, it is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Top-Line Numbers
&lt;/h2&gt;

&lt;p&gt;Here is the dashboard as of mid-March 2026, roughly one month after launch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3k1gqco303qne7vmbto.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3k1gqco303qne7vmbto.webp" alt="topline metrics image" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me break down what each of these numbers actually means.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Downloads Come From
&lt;/h2&gt;

&lt;p&gt;90.6% of downloads came from &lt;strong&gt;App Store Search.&lt;/strong&gt; People searched for something (habit tracker, app blocker, screen time) and found Habit Doom. 4.2% came from App Store Browse (being featured in categories). 3.1% from App Referrer, and a trickle from Web Referrer.&lt;/p&gt;

&lt;p&gt;This tells me two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;My App Store listing is working.&lt;/strong&gt; A 7.94% conversion rate from product page view to download is solid for a new app. People who find it are interested enough to download.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nobody knows this app exists.&lt;/strong&gt; 90.6% organic search means I have done essentially zero marketing. The downloads I have are from people actively searching for this type of app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;By country:&lt;/strong&gt; United States (22), Canada (10), India (10), United Kingdom (7), Saudi Arabia (5). The app is resonating in English-speaking markets, which makes sense given the listing is only in English.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;By device:&lt;/strong&gt; iPhone 83 (86.5%), iPad 13 (13.5%). I did not expect iPad to be 13% but I am glad I added iPad support.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Onboarding Funnel
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. I track every step of onboarding with PostHog:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;40 people started onboarding&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;36 completed it&lt;/strong&gt; (90% completion rate)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average time:&lt;/strong&gt; 1 minute 19 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;90% onboarding completion is genuinely good. The onboarding flow I rebuilt three times is working. People understand what the app does and how to set it up.&lt;/p&gt;

&lt;p&gt;But then comes the critical drop-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;36 completed onboarding&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;19 created their first habit&lt;/strong&gt; (52.78%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;17 dropped off&lt;/strong&gt; (47.22%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost half of the people who successfully complete onboarding never create a single habit. They understand the app. They set it up. Then they leave. This is the biggest problem in my funnel right now, and the number one thing I need to fix.&lt;/p&gt;

&lt;p&gt;There is also a Screen Time permission challenge. The app needs access to Apple's Screen Time API to lock apps — that is the core feature. Roughly a third of users deny this permission during onboarding. Without it, the app is just a habit tracker with&lt;br&gt;
no locking. I need to do a better job explaining why this permission matters before asking for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engagement: The Bright Spot
&lt;/h2&gt;

&lt;p&gt;The people who stick around are using the app a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;21.8 sessions per active device&lt;/strong&gt; (people open the app over 20 times per day on average)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;13 daily active users&lt;/strong&gt; (last 7 days)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~150 daily check-ins&lt;/strong&gt; across all users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;46.5% daily habit completion rate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2,766 total check-ins&lt;/strong&gt; completed (all time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;408 total habits created&lt;/strong&gt; (all time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;21.8 sessions per active device is a big number. It means the people who use Habit Doom are checking in throughout the day: completing habits, checking their progress, unlocking their apps. The app is not a "download and forget" experience for active users. It is part of their daily routine.&lt;/p&gt;

&lt;p&gt;The most popular habits people create: &lt;strong&gt;Read, Workout, Drink Water, Meditate, Go on a Walk.&lt;/strong&gt; These are exactly the habits I was hoping people would track.&lt;/p&gt;

&lt;p&gt;One metric I find fascinating: the &lt;strong&gt;reward usage split.&lt;/strong&gt; About 55% of lock events are manual (users choosing to lock their apps early) versus 45% from the timer expiring automatically. This means more than half the time, users are voluntarily locking their apps before their time runs out. They are actively choosing to use less screen time than they earned. That is the behavior change I was hoping for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Retention Problem
&lt;/h2&gt;

&lt;p&gt;Here is the hard part.&lt;/p&gt;

&lt;p&gt;Weekly stickiness drops from about 55% on day 0 to 7.5% by day 7. That means of the people who use the app on any given day, only 7.5% are still using it a week later.&lt;/p&gt;

&lt;p&gt;DAU/MAU ratio sits around 10%. Industry benchmarks for habit and productivity apps are typically 15-25%. I am below where I need to be.&lt;/p&gt;

&lt;p&gt;22 people have deleted the app. That is 27% of total downloads. Some of those are people who denied the Screen Time permission and could not use the core feature. Some are people who tried it and decided it was not for them. Some are probably people I&lt;br&gt;
lost at the "create your first habit" step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Numbers Are Telling Me
&lt;/h2&gt;

&lt;p&gt;Looking at the full funnel, the story is clear:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is working:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Store listing and conversion (7.94% is healthy)&lt;/li&gt;
&lt;li&gt;Onboarding flow (90% completion)&lt;/li&gt;
&lt;li&gt;Core engagement for retained users (21.8 sessions/day, 150+ daily check-ins)&lt;/li&gt;
&lt;li&gt;The right habits being created (Read, Workout, Meditate)&lt;/li&gt;
&lt;li&gt;Users voluntarily locking apps early (the behavior change is real)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What is broken:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Onboarding-to-first-habit drop-off (47% never create a habit)&lt;/li&gt;
&lt;li&gt;Screen Time permission denial rate (roughly a third)&lt;/li&gt;
&lt;li&gt;Week 1 retention (55% → 7.5%)&lt;/li&gt;
&lt;li&gt;Zero marketing or distribution (90.6% is organic search)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app works. The people who use it, love it. But too many people drop off between "I downloaded this" and "I created my first habit," and too many people leave in the first week.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Am Doing Next
&lt;/h2&gt;

&lt;p&gt;Based on these numbers, here is my plan:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tb4dqm5w91je4oo3xz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3tb4dqm5w91je4oo3xz9.png" alt="New onboarding" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Fix the onboarding-to-habit gap.&lt;/strong&gt; 47% of people who complete onboarding never create a habit. I need to either include habit creation as part of onboarding or make the first-habit-creation screen impossible to miss.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Better Screen Time permission explanation.&lt;/strong&gt; Before asking for the permission, I need to show users exactly what they get: a 5-second demo of apps being locked and unlocked. Make the value undeniable before the system popup appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. First-week retention push.&lt;/strong&gt; The biggest drop happens between day 1 and day 7. I need better notifications — not "reminder to check in" spam, but meaningful nudges tied to streaks and progress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Start marketing.&lt;/strong&gt; 97 downloads from organic search alone is a signal that demand exists. People are searching for this. I need to start writing content, posting on social media, and getting the word out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Fix the IAP flow.&lt;/strong&gt; &lt;strong&gt;Update:&lt;/strong&gt; All three IAPs — Monthly ($2.99/mo), Yearly ($19.99/yr with 14-day free trial), and Lifetime ($34.99) — are now approved and live on the App Store. It took two weeks of fighting App Store Connect bugs and recreating products from scratch due to a product ID mismatch. The full story is &lt;a href="https://habitdoom.com/blog/app-store-connect-iap-bug" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Version
&lt;/h2&gt;

&lt;p&gt;97 downloads in a month is not a success story. It is a starting point.&lt;/p&gt;

&lt;p&gt;But looking at these numbers, I am not discouraged. 21.8 sessions per active device tells me the core product works. 2,766 check-ins tells me people are building habits. Users voluntarily locking their apps early tells me the behavior change is real.&lt;/p&gt;

&lt;p&gt;The problem is not the product. The problem is that not enough people know about it, too many people drop off before they experience the value, and I have not started charging money yet.&lt;/p&gt;

&lt;p&gt;Every one of those is fixable. None of them require me to rebuild the app. They require better onboarding, better marketing, and a working payment flow.&lt;/p&gt;

&lt;p&gt;I shipped 4 failed apps before this one. I fought 21 bugs to get the core technology working. I am not going to stop at 97 downloads.&lt;/p&gt;

&lt;p&gt;Next month I will share these numbers again. They will either be higher or I will tell you exactly why they are not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; is free on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;. If you have read this far, you might as well be download number 98.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>ios</category>
      <category>startup</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>The AI Agent That Saved My App</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Mon, 23 Mar 2026 13:36:45 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/the-ai-agent-that-saved-my-app-5fj4</link>
      <guid>https://dev.to/the_unmanaged_boy/the-ai-agent-that-saved-my-app-5fj4</guid>
      <description>&lt;p&gt;I want to tell you about January 24, 2026.&lt;/p&gt;

&lt;p&gt;That was the day I wrote “Fixed all issues with Automatic App Locking” in my code log. Two saves later, same day: “Fixed auto app blocking.” It was not fixed. Not even close. I would write the word “Fixed” nine more times over the next six days before the thing actually worked.&lt;/p&gt;

&lt;p&gt;This is the real story of building &lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt;, an app that &lt;a href="https://habitdoom.com/blog/how-habit-doom-works" rel="noopener noreferrer"&gt;locks your distracting apps until you complete your daily habits&lt;/a&gt;. Not the polished version. The version with 21 things that broke, an AI assistant that helped me survive, and a moment on January 30 that I will never forget.&lt;/p&gt;

&lt;p&gt;If you have ever tried to build something (an app, a business, a creative project) and wondered whether the struggle is normal, it is. This is what normal looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Was Trying to Build
&lt;/h2&gt;

&lt;p&gt;The idea is simple: pick the apps that distract you (Instagram, TikTok, YouTube), set daily habits you want to build (reading, exercise, drinking water), and the apps stay locked until the habits are done. &lt;a href="https://habitdoom.com/blog/why-i-built-habit-doom" rel="noopener noreferrer"&gt;I have written about why I built it&lt;/a&gt; and &lt;a href="https://habitdoom.com/blog/growth-mindset-habits" rel="noopener noreferrer"&gt;what I learned from failing at it multiple times before&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But I have never written about the building itself. The parts that made me want to throw my laptop out the window.&lt;/p&gt;

&lt;p&gt;The app needed to do three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lock apps.&lt;/strong&gt; Real locks that you cannot bypass by closing the app or restarting your phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlock apps when habits are done.&lt;/strong&gt; With a timer so screen time is earned, not unlimited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-lock apps automatically.&lt;/strong&gt; When the timer runs out, and again at midnight for the next day.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Number 1 was doable. Number 2 was fine. Number 3 nearly killed the entire project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting From Zero
&lt;/h2&gt;

&lt;p&gt;I am a product manager by day. Not a professional developer. I had built &lt;a href="https://habitdoom.com/blog/growth-mindset-habits" rel="noopener noreferrer"&gt;a few apps before&lt;/a&gt;, none of them successful, but I was far from an expert.&lt;/p&gt;

&lt;p&gt;I started by taking an iOS development course on Udemy. It was genuinely great. The fundamentals clicked. I understood how to build screens, handle data, make things look right.&lt;/p&gt;

&lt;p&gt;But there is a massive gap between "I finished an online course" and "I can ship a real app." I was taking entire days to build screens that should have taken an hour. So I tried an AI code editor called Cursor. Suddenly I was moving three times faster. The AI handled the repetitive parts. I handled the decisions.&lt;/p&gt;

&lt;p&gt;Then I hit the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wall: Apple's Screen Time API
&lt;/h2&gt;

&lt;p&gt;To lock apps on iPhone for real (not just show a “please stop” popup, but actually lock them so they cannot be opened), you need to use Apple’s Screen Time API. It is the only way. And it is one of the most poorly documented APIs Apple has ever released.&lt;/p&gt;

&lt;p&gt;It was originally built for parental controls. The community of developers using it is tiny. Most of the problems I ran into had no answers on the internet. None.&lt;/p&gt;

&lt;p&gt;Here is the thing I learned the hard way: one lock is not enough.&lt;br&gt;
The app can be closed. The phone can be restarted. The user can ignore it for hours. The lock has to survive all of that. It has to re-engage at midnight. It has to re-lock when the timer expires, even if the app is not running.&lt;/p&gt;

&lt;p&gt;I ended up building three independent locking systems that all do the same thing:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.habitdoom.com%2Fblog%2F21-bugs-that-nearly-killed-my-app%2Ftriple-blocking-architecture.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.habitdoom.com%2Fblog%2F21-bugs-that-nearly-killed-my-app%2Ftriple-blocking-architecture.webp" alt="Triple-blocking architecture diagram showing Shield Extension, DeviceActivity Monitor, and Main App all independently blocking&amp;lt;br&amp;gt;
  apps" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1&lt;/strong&gt; catches the lock when you try to open a blocked app. &lt;strong&gt;Layer 2&lt;/strong&gt; runs in the background on a schedule, checking if your timer has expired. &lt;strong&gt;Layer 3&lt;/strong&gt; checks the moment you open Habit Doom itself.&lt;/p&gt;

&lt;p&gt;Three systems. All doing the same job. Because any single one can fail silently, and if the lock fails even once, the entire app loses its purpose.&lt;/p&gt;

&lt;p&gt;Belt and suspenders and duct tape.&lt;/p&gt;

&lt;h2&gt;
  
  
  21 Things That Broke
&lt;/h2&gt;

&lt;p&gt;I kept a list. Here is every major bug I hit while building this app. Some of them took hours. Some took days. Some took weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Screen Time API (1–8):&lt;/strong&gt; Apps would not re-lock at midnight. Background schedules would not fire if set more than 45 minutes in the future. One API call would silently trigger another, which would re-lock ALL apps in the middle of the night, while users were supposed to have them unlocked. A flag that tracks whether apps are locked would go stale when the app was in the background, causing the whole system to make wrong decisions. Timestamps with second precision would not match schedules with minute precision, causing off-by-one errors. A function designed to restore blocking would, if it hit a data error, permanently destroy its own data instead. And the re-locking code could get suspended halfway through if the user switched to another app, leaving apps in a half-locked state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The communication problem (9–10):&lt;/strong&gt; The three locking systems run as separate processes on your phone. They cannot share a database. Everything has to be communicated through a tiny shared storage space, with data manually packed and unpacked. One system used the label "quota_expired" for when your time runs out. Another used "reblock_expired." Same event, different names. They could not understand each other. A typo-level problem that took hours to find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The interface (11–15):&lt;/strong&gt; Animations would flash and glitch when you checked off a habit. The progress bar on the lock screen timer would freeze. Buttons would appear and disappear at the wrong times. The app's new programming language update broke one of my fixes because it considered the fix a safety violation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple's own tools (16–20):&lt;/strong&gt; This one deserves its own section.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Apple's Tools Break
&lt;/h2&gt;

&lt;p&gt;These were not bugs in my code. These were bugs in the tools Apple gives developers to submit apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsm9ptbf5mstj25pbad3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsm9ptbf5mstj25pbad3.webp" alt="App Store Connect version page with the In-App Purchases section completely missing" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I renamed a version of my app in App Store Connect (Apple's developer portal) and the entire section for managing in-app purchases vanished. No error. No warning. Just gone.&lt;/p&gt;

&lt;p&gt;I created a fresh version. Still missing. I tried again. And again. Four versions (v1.5, v1.6, v1.7) and the section never came back. A week of going in circles.&lt;/p&gt;

&lt;p&gt;I eventually had to bypass Apple's broken website entirely and use their programming interface (API) to submit my in-app purchase manually. The API worked. The website was broken. This is Apple's own tool.&lt;/p&gt;

&lt;p&gt;Meanwhile, the EU compliance form got stuck on a loading spinner. My subscription plans were blocked because they needed the missing section. One UI bug cascaded into three blocked revenue streams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update (March 21, 2026):&lt;/strong&gt; The IAP saga got worse before it got better. After the API workaround, I discovered a product ID mismatch that forced me to recreate all three products from scratch. The full ordeal took over two weeks. All IAPs are now approved and live. &lt;a href="https://habitdoom.com/blog/app-store-connect-iap-bug" rel="noopener noreferrer"&gt;Full story here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 21&lt;/strong&gt; was not a code bug at all: 75 downloads, one review (one star), and 20 people had already deleted the app, all while I was fighting infrastructure instead of improving the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug That Almost Ended Everything
&lt;/h2&gt;

&lt;p&gt;There was one bug that nearly made me quit for good.&lt;/p&gt;

&lt;p&gt;The automatic re-locking (the entire reason the app exists) worked perfectly when connected to my development tools. The moment I disconnected and ran it on my actual phone: nothing. The timer would expire and apps would stay unlocked.&lt;/p&gt;

&lt;p&gt;This is the worst kind of bug. It works when you are watching it. It breaks when you are not. And you cannot even investigate it properly because the act of investigating changes the behavior.&lt;/p&gt;

&lt;p&gt;I spent days on this. I could not figure it out.&lt;/p&gt;

&lt;p&gt;I gave up. Not dramatically. Just quietly. I stopped opening the project. I told myself I would come back to it. "Later" started stretching into weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI That Did Not Give Up
&lt;/h2&gt;

&lt;p&gt;I had been using Cursor for building the interface, and it was great for that. But these bugs were different. They were not "make this screen look right" problems. They were "why do three invisible background systems disagree about whether your apps should be locked" problems.&lt;/p&gt;

&lt;p&gt;I decided to try &lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;, an AI coding assistant that runs in your terminal. Not because I expected it to magically fix everything. I was out of ideas and had nothing to lose.&lt;/p&gt;

&lt;p&gt;It struggled too. I want to be honest about that. The Screen Time API is so poorly documented that even an AI trained on the entire internet did not have clean answers. We would try something. It would fail. We would try something else. The sessions were long.&lt;/p&gt;

&lt;p&gt;But here is what made the difference: &lt;strong&gt;it never got frustrated, and it could see everything at once.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I was trying to figure out why the locking failed at midnight, Claude Code could look at all three locking systems simultaneously. It could trace a piece of data from where it was saved in one system to where it was read in another. It could spot that a flag was being set in one place but going stale in another.&lt;/p&gt;

&lt;p&gt;We debugged together. That is the most accurate way to describe it. I knew what the app was supposed to do. Claude Code could reason about&lt;br&gt;
what was actually happening across the entire codebase. Together, we were more effective than either of us alone.&lt;/p&gt;

&lt;p&gt;I tried to find our exact conversation to share here. Claude Code does not save session transcripts by default. But I asked it to look at my code history, and the story it found was better than any screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  97 Days Told Through Code
&lt;/h2&gt;

&lt;p&gt;I asked Claude Code to look at my code history and tell me the story. Here is what 97 days of building an app actually looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;December 13.&lt;/strong&gt; Initial Commit. Day zero. An empty project with a dream.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;December 18–23.&lt;/strong&gt; Home screen, habit creation, streaks, archive, vacation mode, onboarding. Ten days in and moving fast. Everything felt possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;December 29.&lt;/strong&gt; Tested ScreenTime feature but failed.&lt;/p&gt;

&lt;p&gt;The first wall. Apple's Screen Time API does not just work. That single line in my code history hides days of confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;December 31 – January 4.&lt;/strong&gt; New Year's Eve, still coding. Added Hard Mode. Widgets land four days later. I was building around the wall I could not get through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;January 19.&lt;/strong&gt; I gave up on the hardest feature.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Removed progressive unlocking and did general clean up&lt;br&gt;
Remove background processes for progressive unlocking&lt;br&gt;
Release 1.0.2&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I ripped out the automatic re-locking entirely and shipped without it. The app was live, but missing its core promise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;January 23–26.&lt;/strong&gt; I went back in. Four "fixed app locking" entries across four days:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fixed all issues with app locking incorrectly&lt;br&gt;
Fixed all issues with Automatic App Locking&lt;br&gt;
Fixed auto app blocking&lt;br&gt;
Fixed some edge cases with automatic app locking&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"Fixed ALL issues." Then two days later: "Fixed some edge cases." The quiet, exhausted version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;January 28.&lt;/strong&gt; I tried progressive unlocking again. The feature I had ripped out nine days earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;January 30:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MILESTONE 1: Progressive unlocking + Midnight unlocking Acheved&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0keeeeswfz79ua3kdmx7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0keeeeswfz79ua3kdmx7.webp" alt="Claude Code terminal showing the MILESTONE 1 commit and a recreation of the moment midnight re-blocking finally worked" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;48 days from first commit. The core loop finally works. The typo in "Acheved" is still there. I am never fixing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;February 3.&lt;/strong&gt; Rewrote the entire app. Not a refactor. A rewrite. To support global app blocking instead of per-habit blocking. Brave or insane, probably both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;February 5.&lt;/strong&gt; Live Activity support. Your unlock timer now shows on the lock screen with a real-time countdown in the Dynamic Island.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;February 17.&lt;/strong&gt; Discovered Apple's background scheduling is unreliable past 45 minutes. Built a workaround that chains shorter checks together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;February 22:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;MILESTONE 2: Rebuild entire app with single timer quota&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Second full rewrite in three weeks. One shared timer across all habits instead of separate timers for each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;February 28 – March 8.&lt;/strong&gt; Payment screens, in-app purchases, milestone celebrations, widgets, weekly summary. The app is feature-complete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 10–14.&lt;/strong&gt; Polish. Massive onboarding improvements. iPad layout. Making the first impression count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;March 17.&lt;/strong&gt; Fixed midnight blocking when active session.&lt;/p&gt;

&lt;p&gt;Still fixing the blocking system. 94 days in. It never stops fighting back.&lt;/p&gt;




&lt;p&gt;97 days. Around 100 code entries. 2 full rewrites. 5 milestones. 1 indie dev.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Building With AI Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;I want to be honest about this because there is a lot of hype around AI tools right now.&lt;/p&gt;

&lt;p&gt;Claude Code did not build Habit Doom for me. It built Habit Doom &lt;strong&gt;with&lt;/strong&gt; me. I brought the knowledge of what the app should do, the product decisions, and the gut feeling for when something was wrong. It brought tireless patience, the ability to hold the entire project in its head, and a willingness to try one more thing when I was ready to quit.&lt;/p&gt;

&lt;p&gt;If you are considering using AI to help build something: it is not a replacement for understanding what you are building. It is a multiplier on the understanding you already have. The more you know, the more useful it becomes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Am Now
&lt;/h2&gt;

&lt;p&gt;As I write this, Habit Doom has about 75 downloads. One review. One star. Twenty deletions.&lt;/p&gt;

&lt;p&gt;The blocking works. All three layers, the midnight resets, the timer-based re-locking. The foundation is solid. But there is a long distance between "the technology works" and "people love this."&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://habitdoom.com/blog/growth-mindset-habits" rel="noopener noreferrer"&gt;shipped 4 failed apps before this one&lt;/a&gt;. The difference this time is that I am not interpreting small numbers as a verdict. I am interpreting them as day one.&lt;/p&gt;

&lt;p&gt;Twenty-one bugs nearly killed this app. I am still here. The app is still here. And every night at midnight, the triple-locking system fires, resets everything, and waits for me to earn my screen time tomorrow.&lt;/p&gt;

&lt;p&gt;That is the whole story. Not the polished version. The real one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; is free on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;. It takes 30 seconds to set up. And yes, the automatic re-locking works now. I checked.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>ai</category>
      <category>agents</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>2 Weeks to Ship 3 Buttons on iOS</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Mon, 23 Mar 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/2-weeks-to-ship-3-buttons-on-ios-5fl2</link>
      <guid>https://dev.to/the_unmanaged_boy/2-weeks-to-ship-3-buttons-on-ios-5fl2</guid>
      <description>&lt;p&gt;Three buttons. Monthly, Yearly, Lifetime. That is all I needed to add to &lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt;, an iOS app that [locks your distracting apps until you do your habits (&lt;a href="https://habitdoom.com/blog/how-habit-doom-works" rel="noopener noreferrer"&gt;https://habitdoom.com/blog/how-habit-doom-works&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The code took two days. Getting those buttons live on the App Store took two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug
&lt;/h2&gt;

&lt;p&gt;I had version 1.4 live on the App Store. I renamed it to v1.5 in App Store Connect to submit it with in-app purchases. The rename went through fine.&lt;/p&gt;

&lt;p&gt;Except the “In-App Purchases and Subscriptions” section was gone. Not collapsed. Not behind a toggle. Gone. The entire section where you attach IAPs to a version for review had vanished from the page.&lt;/p&gt;

&lt;p&gt;IAP section: gone The entire section vanished from App Store Connect&lt;br&gt;
I assumed I had done something wrong. So I created a fresh version, v1.6, via “+ Version or Platform.” New version, clean slate.&lt;br&gt;
Still missing.&lt;/p&gt;

&lt;p&gt;v1.7. Missing. v1.8. Missing. v1.9. Missing.&lt;/p&gt;

&lt;p&gt;Different browsers. Cache cleared. Different Apple IDs. Nothing worked. Four versions, same hole in the page.&lt;/p&gt;

&lt;p&gt;## Apple Support&lt;/p&gt;

&lt;p&gt;I was about to file a support case. But by that time a new bug showed up.&lt;/p&gt;

&lt;p&gt;The EU Digital Services Act compliance form had its own bug, an infinite loading spinner that never resolved. A separate support case. A separate wait.&lt;/p&gt;

&lt;p&gt;One UI bug was now blocking three revenue streams: Monthly, Yearly, and Lifetime. My app was live. Users were downloading it. And I could not charge money because Apple’s website was broken.&lt;/p&gt;

&lt;p&gt;Then this email landed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd37xj05i0rkjdat219cw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd37xj05i0rkjdat219cw.webp" alt="Email of a user describing their problem" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.habitdoom.com%2Fblog%2F21-bugs-that-nearly-killed-my-app%2Fuser-email-purchase-bug.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.habitdoom.com%2Fblog%2F21-bugs-that-nearly-killed-my-app%2Fuser-email-purchase-bug.webp" alt="A user reporting that the purchase button does not show up and keeps loading, even after reinstalling&amp;lt;br&amp;gt;
  the app" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what they saw: the paywall loaded, but there were no purchase buttons. Just an infinite spinner where the pricing should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API Detour
&lt;/h2&gt;

&lt;p&gt;After a week of dead ends, I tried bypassing the web UI entirely using the App Store Connect REST API. I generated an API key, looked up my Lifetime purchase’s inAppPurchaseV2 ID, and submitted it programmatically.&lt;/p&gt;

&lt;p&gt;The API accepted it. The Lifetime IAP went into review alongside v1.7. I thought I had found the fix.&lt;/p&gt;

&lt;p&gt;I was wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem
&lt;/h2&gt;

&lt;p&gt;When v1.7 shipped and I tested the live purchase flow, nothing worked. StoreKit returned zero products. The paywall was empty.&lt;br&gt;
The reason: my code used com.unmanagedboy.habitdoom.monthly but App Store Connect had com.unmanagedboy.habitdoom.pro.monthly. An extra .pro. in the path. A typo I made when creating the products two weeks earlier.&lt;br&gt;
Here is the thing about product IDs in App Store Connect: you cannot change them. Ever. Once created, they are permanent. The only option is to create entirely new products.&lt;br&gt;
So the API workaround had submitted the wrong product. The Monthly and Yearly API submissions had also failed with DEVELOPER_ACTION_NEEDED. A week of work on the API, wasted.&lt;br&gt;
Product IDs are forever You cannot change them in App Store Connect. Get them right the first time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Fixed It
&lt;/h2&gt;

&lt;p&gt;I created three brand new products with the correct IDs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnle4y615nwywicobdhw6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnle4y615nwywicobdhw6.webp" alt="product ids" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each one needed pricing across 175 territories, localization, review screenshots, subscription group setup, and a 14-day free trial offer for Yearly.&lt;/p&gt;

&lt;p&gt;Then I created a fresh version (v1.10) and the IAP section appeared. Just like that. The section that had been missing for a week across four different versions was suddenly there, working perfectly.&lt;br&gt;
I attached all three IAPs through the UI. Submitted for review. Approved in less than 24 hours.&lt;/p&gt;

&lt;p&gt;The old v1 products sit abandoned in App Store Connect with “Developer Action Needed” status. A monument to a two-week detour caused by one misplaced .pro. in a product ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Product IDs are permanent.&lt;/strong&gt; Copy-paste them from your code into App Store Connect. Do not type them.&lt;br&gt;
One typo means recreating everything from scratch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The first IAP submission must be linked to a binary.&lt;/strong&gt; When you submit IAPs or subscriptions for the&lt;br&gt;
very first time, they need to be attached to an app version with a binary. After that first approval, they&lt;br&gt;
can be managed separately. This is easy to miss and not obvious from the UI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do not remove your app submission after attaching IAPs.&lt;/strong&gt; If you have already submitted a version&lt;br&gt;
with IAPs attached and you pull or remove the app submission, the IAPs get detached and you are back to&lt;br&gt;
square one. Submit once and leave it alone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Local StoreKit testing hides ID mismatches.&lt;/strong&gt; Your &lt;code&gt;.storekit&lt;/code&gt; config file has its own product IDs.&lt;br&gt;
Tests pass perfectly while the live configuration is completely wrong. Test against the sandbox, not just&lt;br&gt;
the local config.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do not rename versions in ASC.&lt;/strong&gt; Always create fresh ones via "+ Version or Platform." Renaming&lt;br&gt;
triggered the vanishing IAP section for me.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If the IAP section is missing, try creating new products.&lt;/strong&gt; In my case, creating fresh products with&lt;br&gt;
new IDs made the section reappear. No API workaround needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The code is the easy part.&lt;/strong&gt; StoreKit 2 is genuinely well-designed. The App Store Connect&lt;br&gt;
configuration, agreements, tax forms, and UI bugs are what eat your time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Budget two weeks for IAP integration,&lt;/strong&gt; not two days. The code is fast. Everything around it is slow,&lt;br&gt;
broken, or both.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a company that makes $100 billion a year from the App Store, a broken IAP section is a rounding error. For a solo developer with82 downloads and $0 in revenue](&lt;a href="https://habitdoom.com/blog/what-82-ios-downloads-taught-me" rel="noopener noreferrer"&gt;https://habitdoom.com/blog/what-82-ios-downloads-taught-me&lt;/a&gt;), it was a gut punch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; is live on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App&lt;br&gt;
  Store&lt;/a&gt;. All three buttons work. It&lt;br&gt;
  just took two weeks to ship them.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>indiedev</category>
      <category>appstore</category>
    </item>
    <item>
      <title>Apple's Screen Time API Broke Me</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Mon, 23 Mar 2026 12:07:00 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/apples-screen-time-api-broke-me-1d00</link>
      <guid>https://dev.to/the_unmanaged_boy/apples-screen-time-api-broke-me-1d00</guid>
      <description>&lt;p&gt;I spent 97 days building &lt;a href="https://habitdoom.com/" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt;, an iOS app that locks your distracting apps until you complete your daily habits. The hardest part was not the UI, the business logic, or the App Store review process. It was Apple's Screen Time API.&lt;/p&gt;

&lt;p&gt;This is everything I learned: the stuff Apple's documentation does not tell you, the behaviors that will break your app silently, and the architecture I ended up with after &lt;a href="https://habitdoom.com/blog/21-bugs-that-nearly-killed-my-app" rel="noopener noreferrer"&gt;21 bugs nearly killed the project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building anything that locks or monitors apps on iOS, this might save you weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Frameworks
&lt;/h2&gt;

&lt;p&gt;Apple's "Screen Time API" is actually three separate frameworks that work together:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FamilyControls&lt;/strong&gt; handles authorization. Your app requests permission from the user to monitor and restrict their device activity. This is the gatekeeper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ManagedSettings&lt;/strong&gt; handles the actual blocking. You create a &lt;code&gt;ManagedSettingsStore&lt;/code&gt; and set &lt;code&gt;store.shield.applications&lt;/code&gt; to a set of app tokens. Those apps are now locked at the OS level. Users see a shield screen when they try to open them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeviceActivity&lt;/strong&gt; handles scheduling. You can set up monitoring schedules that trigger your code when intervals start or end. This is how you schedule re-blocking events.&lt;/p&gt;

&lt;p&gt;The documentation for all three is thin. Some classes have one-line descriptions. Some behaviors are not documented at all. Here is what I learned the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: One Blocking Mechanism Is Never Enough
&lt;/h2&gt;

&lt;p&gt;This is the most important lesson in this entire post.&lt;/p&gt;

&lt;p&gt;If your app blocks other apps, the blocking has to survive the user closing your app, force-quitting it, restarting their phone, or ignoring it for 12 hours. A single blocking mechanism cannot handle all of these scenarios.&lt;/p&gt;

&lt;p&gt;I ended up with three independent systems that all enforce the same lock:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shield Extension&lt;/strong&gt; runs synchronously whenever the user tries to open a blocked app. It checks if it is a new day or if the unlock timer has expired, and re-blocks if needed. This is your first line of defense because it fires before the app can even open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeviceActivity Monitor&lt;/strong&gt; runs on a schedule in the background. It proactively checks whether the unlock timer has expired and re-blocks apps even when the user is not trying to open them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main App&lt;/strong&gt; fires when your app comes to the foreground, immediately checks the shared state, and re-blocks if needed. This runs before the database initializes, before the UI renders: a synchronous check with zero async calls.&lt;/p&gt;

&lt;p&gt;Why all three? Because each one covers the gaps of the others. The Shield Extension only fires when the user opens a blocked app. The DeviceActivity Monitor only fires on schedule (and schedules can be unreliable, more on that below). The Main App only fires when the user opens your app. Together, they cover every scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: DeviceActivity Schedules Are Unreliable Beyond 45 Minutes
&lt;/h2&gt;

&lt;p&gt;This one took weeks to figure out and there is no documentation about it anywhere.&lt;/p&gt;

&lt;p&gt;If you schedule a DeviceActivity monitoring event for 90 minutes in the future, it might fire at 60 minutes. Or it might not fire at all. Apple's scheduling system simply is not reliable for events far in the future.&lt;/p&gt;

&lt;p&gt;The workaround: &lt;strong&gt;chain shorter schedules together.&lt;/strong&gt; Schedule checks at 15, 30, and 44 minutes. When each check fires, it looks at the shared state to see if the unlock timer has expired. If it has not expired yet, it chains the next check, scheduling another event 16 minutes out (or targeting 1 minute past the expected expiry if that is closer).&lt;/p&gt;

&lt;p&gt;Why 44 minutes and not 45? Because you want to stay safely within the reliable window with a margin of error.&lt;/p&gt;

&lt;p&gt;Why rotate through multiple schedule names? Because if two schedules try to use the same &lt;code&gt;DeviceActivityName&lt;/code&gt;, the second one overwrites the first. Use rotating slot names like &lt;code&gt;habitDoomReblockChain_0&lt;/code&gt; through &lt;code&gt;habitDoomReblockChain_4&lt;/code&gt; so they do not clobber each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: startMonitoring() Has a Devastating Side Effect
&lt;/h2&gt;

&lt;p&gt;When you call &lt;code&gt;center.startMonitoring()&lt;/code&gt; for a &lt;code&gt;DeviceActivityName&lt;/code&gt; that is already being monitored, it first calls &lt;code&gt;stopMonitoring()&lt;/code&gt; internally. This triggers &lt;code&gt;intervalDidEnd&lt;/code&gt; on your &lt;code&gt;DeviceActivity&lt;/code&gt; Monitor extension.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;intervalDidEnd&lt;/code&gt; handler re-blocks all apps (which is a reasonable thing for it to do), then calling &lt;code&gt;startMonitoring()&lt;/code&gt; to update a schedule will &lt;strong&gt;re-block all apps as a side effect&lt;/strong&gt;, even in the middle of an active unlock session.&lt;/p&gt;

&lt;p&gt;I discovered this when users reported their apps getting locked at 1 AM for no reason. The cause: a scheduled re-check was calling &lt;code&gt;startMonitoring()&lt;/code&gt; to chain the next check, which triggered &lt;code&gt;intervalDidEnd&lt;/code&gt;, which re-blocked everything.&lt;/p&gt;

&lt;p&gt;The fix: add a guard in your &lt;code&gt;intervalDidEnd&lt;/code&gt; handler that checks whether the current unlock session is still active before re-blocking. Do not assume that &lt;code&gt;intervalDidEnd&lt;/code&gt; means "the user's time is up."&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: Extensions Cannot Share a Database
&lt;/h2&gt;

&lt;p&gt;Your Shield Extension, DeviceActivity Monitor, and main app all run as separate processes. They cannot share a SwiftData or Core Data database.&lt;/p&gt;

&lt;p&gt;The only reliable way to share state between them is &lt;strong&gt;UserDefaults via App Groups.&lt;/strong&gt; This means manually serializing everything (app tokens, category tokens, web domain tokens, timestamps, flags) into UserDefaults keys.&lt;/p&gt;

&lt;p&gt;Here is the minimum shared state I ended up needing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;isCurrentlyUnlocked&lt;/code&gt;: whether an unlock session is active&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;quotaEndTimestamp&lt;/code&gt;: when the unlock timer expires&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;globalSerializedTokens&lt;/code&gt;: the app tokens to re-block (base64 JSON)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;globalSerializedCategoryTokens&lt;/code&gt;: same for categories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;globalSerializedWebDomainTokens&lt;/code&gt;: same for web domains&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lastShieldDayCheck&lt;/code&gt;: when the Shield Extension last checked for a new day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Serialize carefully. If the token data fails to decode and you write an empty set back to UserDefaults, you have permanently destroyed your blocking data. Always validate before overwriting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 5: You Must Block Apps, Categories, AND Web Domains
&lt;/h2&gt;

&lt;p&gt;When you apply a shield, you need to set three properties on your ManagedSettingsStore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;store.shield.applications&lt;/code&gt;: blocks individual apps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store.shield.applicationCategories&lt;/code&gt;: blocks app categories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;store.shield.webDomains&lt;/code&gt;: blocks web domains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you forget web domains, users can access Instagram through Safari. If you forget categories, entire groups of apps slip through. I missed this across three different extension files and had to fix it in all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 6: The Stale Flag Problem
&lt;/h2&gt;

&lt;p&gt;Your main app sets &lt;code&gt;isCurrentlyUnlocked = true&lt;/code&gt; when the user starts an unlock session. It starts a timer to set it back to &lt;code&gt;false&lt;/code&gt; when the session expires.&lt;/p&gt;

&lt;p&gt;But if the user backgrounds your app, that timer stops. The flag stays &lt;code&gt;true&lt;/code&gt; indefinitely. Your extensions read the flag and think apps should still be unlocked, even though the timestamp says the session expired 2 hours ago.&lt;/p&gt;

&lt;p&gt;The fix: ** ManagedSettingsStore**. Always check the timestamp. If isCurrentlyUnlocked is true but quotaEndTimestamp is in the past, the session has expired regardless of what the flag says. Check both in every extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 7: Make Your First Check Synchronous
&lt;/h2&gt;

&lt;p&gt;When your app comes to the foreground after a long background period, there is a race condition. The UI starts rendering, &lt;code&gt;async&lt;/code&gt; tasks kick off, and the database begins initializing, all before you have had a chance to check whether the unlock session has expired.&lt;/p&gt;

&lt;p&gt;The fix: run a synchronous, zero-async check before anything else. Before your SwiftData model container is created, before ContentView renders, read the UserDefaults state and re-block if needed. No &lt;code&gt;await&lt;/code&gt;. No &lt;code&gt;Task&lt;/code&gt;. Just a direct read and a direct write to &lt;code&gt;ManagedSettingsStore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This also means your re-blocking function cannot depend on the database. It has to work entirely from UserDefaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 8: Minute vs Second Precision
&lt;/h2&gt;

&lt;p&gt;DeviceActivity uses &lt;code&gt;DateComponents(hour:, minute:)&lt;/code&gt; for scheduling. Your unlock timestamps use &lt;code&gt;TimeInterval&lt;/code&gt; (seconds since epoch).&lt;/p&gt;

&lt;p&gt;An unlock session that expires at 10:30:45 will not be caught by a DeviceActivity check scheduled for 10:30. The check fires at 10:30:00, reads the timestamp, sees that 10:30:45 is still in the future, and does nothing. The next check might not come for 15 minutes.&lt;/p&gt;

&lt;p&gt;The fix: when scheduling checks near the expected expiry, add a 1-minute buffer. Schedule for 10:31 or 10:32, not 10:30.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 9: Async Re-blocking Gets Suspended
&lt;/h2&gt;

&lt;p&gt;If your re-blocking function uses &lt;code&gt;async/await&lt;/code&gt; and has multiple &lt;code&gt;await&lt;/code&gt; points, iOS can suspend the Task if the user switches to another app. The function stops mid-execution. Apps might be partially blocked: some locked, some not.&lt;/p&gt;

&lt;p&gt;The fix: write a synchronous version of your re-blocking function that runs first, before any async work. It does not need to be perfect. It just needs to apply the shield immediately. The async version can clean up state afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture That Actually Works
&lt;/h2&gt;

&lt;p&gt;After 97 days, &lt;a href="https://habitdoom.com/blog/21-bugs-that-nearly-killed-my-app" rel="noopener noreferrer"&gt;21 bugs&lt;/a&gt;, and two full rewrites of the app, here is the architecture I would recommend to anyone building on the Screen Time API:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Three independent blocking systems.&lt;/strong&gt; Shield Extension, DeviceActivity Monitor, Main App. All three&lt;br&gt;
check and re-block independently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shared state via App Groups UserDefaults.&lt;/strong&gt; Serialize everything carefully. Validate before&lt;br&gt;
overwriting. Always check timestamps alongside boolean flags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chained DeviceActivity schedules.&lt;/strong&gt; Never schedule more than 44 minutes out. Chain checks that each&lt;br&gt;
schedule the next one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Synchronous first check on foreground.&lt;/strong&gt; Before database, before UI, before async. Read UserDefaults,&lt;br&gt;
re-block if expired.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Block apps, categories, AND web domains.&lt;/strong&gt; In every extension that applies a shield.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guard intervalDidEnd.&lt;/strong&gt; Do not assume it means the user's time is up. Check the actual state before&lt;br&gt;
re-blocking.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not elegant. It is defensive to the point of paranoia. But app locking is a feature where one failure destroys the user's trust in your entire product. Being paranoid is the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  One More Thing
&lt;/h2&gt;

&lt;p&gt;The emulator lies. My app blocking worked perfectly in Xcode's simulator. It failed completely on a real device when Xcode was not attached. The debugging environment changes the behavior of background extensions.&lt;/p&gt;

&lt;p&gt;Always test app blocking on a real device, disconnected from Xcode, with the app backgrounded or killed. That is the only environment that matches what your users will experience.&lt;/p&gt;

&lt;p&gt;I hope this saves you some of the 97 days it took me. If you are building something with the Screen Time API and hit a wall, I have probably hit the same wall, so feel free to reach out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; is the app I built with all of this. It locks your apps until your&lt;br&gt;
  habits are done. Free on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App&lt;br&gt;
  Store&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>programming</category>
      <category>founder</category>
    </item>
    <item>
      <title>Two AI Agents Told Me My App Was Impossible. One Shipped It.</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Mon, 23 Mar 2026 10:38:00 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/two-ai-agents-told-me-my-app-was-impossible-one-shipped-it-11k9</link>
      <guid>https://dev.to/the_unmanaged_boy/two-ai-agents-told-me-my-app-was-impossible-one-shipped-it-11k9</guid>
      <description>&lt;p&gt;In December 2025, I asked ChatGPT how to automatically block apps after a timer expires in iOS. The response was clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Programmatic auto-blocking after a certain time is not supported by the Screen Time API. The&lt;br&gt;
  DeviceActivity token is opaque and cannot be used once your app is closed or in the background."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude said the same thing. So did Cursor’s AI. Three tools, same answer: your app is impossible.&lt;/p&gt;

&lt;p&gt;Four months later, &lt;a href="https://habitdoom.com/" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; is on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;. It auto-blocks apps. It works in the background. It does exactly what three AI agents told me could not be done.&lt;br&gt;
Here is what happened.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Was Building
&lt;/h2&gt;

&lt;p&gt;Before I get into the AI tools, let me explain what made this app so hard to build.&lt;br&gt;
&lt;a href="https://habitdoom.com/blog/how-habit-doom-works" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; locks your distracting apps (Instagram, TikTok, YouTube) until you complete your daily habits. Not a timer. Not a gentle reminder. A hard lock at the operating system level. You physically cannot open Instagram until you have done your morning routine.&lt;/p&gt;

&lt;p&gt;To build this on iOS, you need Apple’s &lt;a href="https://habitdoom.com/blog/apple-screen-time-api-guide" rel="noopener noreferrer"&gt;Screen Time API&lt;/a&gt;. And the Screen Time API is not one thing. It is three separate frameworks that need to work together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FamilyControls.&lt;/strong&gt; Asks the user for permission to monitor and block apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ManagedSettings.&lt;/strong&gt; Applies the actual blocks (called "shields") to selected apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeviceActivityMonitor.&lt;/strong&gt; Watches for time-based events and triggers actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the catch: these frameworks do not all run inside your app. The DeviceActivityMonitor runs as a separate process called an “extension.” Think of it as a tiny background program that iOS manages independently. Your main app and this extension need to share data through something called “App Groups,” a shared storage space that both can access.&lt;/p&gt;

&lt;p&gt;If that sounds complicated, it is. And it is where every AI tool I tried started to break.&lt;/p&gt;

&lt;p&gt;I had just finished an iOS development course. I knew Swift and SwiftUI. I had never shipped an app. Here is how each AI tool performed across four months of building.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cursor (Nov–Dec 2025)
&lt;/h2&gt;

&lt;p&gt;Cursor was my first AI coding tool. I used it to build the first version of Habit Doom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Cursor did well:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cursor was fast. It scaffolded the entire UI in days: habit creation screens, settings, navigation, the data model. For standard SwiftUI code, it was significantly faster than writing everything by hand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// Cursor generated clean SwiftUI views like this quickly&lt;/span&gt;
  &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;HabitRow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Habit&lt;/span&gt;

      &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kt"&gt;HStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;habit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purple&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;habit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Current Streak: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;habit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;streak&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; days"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secondary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For lists, navigation stacks, forms, animations, and state management, Cursor handled it. If you are building a standard iOS app without complex system integrations, Cursor is a genuinely good tool.&lt;/p&gt;

&lt;p&gt;Where Cursor broke down:&lt;/p&gt;

&lt;p&gt;The Screen Time API. Cursor works by predicting the next line of code based on your current file. But the Screen Time API requires understanding how three separate targets (your app, the Shield extension, and the Monitor extension) coordinate through shared state.&lt;/p&gt;

&lt;p&gt;Cursor could not hold that in its head. It would suggest API calls that looked right but used wrong types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// What Cursor suggested (does not compile)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ManagedSettingsStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selectedApps&lt;/span&gt;  &lt;span class="c1"&gt;// Wrong — needs Set&amp;lt;ApplicationToken&amp;gt;&lt;/span&gt;
  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationCategories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;  &lt;span class="c1"&gt;// Wrong — needs .specific(tokens)&lt;/span&gt;

  &lt;span class="c1"&gt;// What actually works&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ManagedSettingsStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationTokens&lt;/span&gt;
  &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applicationCategories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;specific&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categoryTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On December 29, 2025, I committed: “Tested ScreenTime feature but failed. Cleaned the rest of the code.” That commit included two troubleshooting documents I had written ( &lt;code&gt;CRITICAL_FIX_NEEDED.md&lt;/code&gt; and &lt;code&gt;EXTENSION_NOT_TRIGGERING_ISSUE.md&lt;/code&gt;) cataloging everything that was broken.&lt;/p&gt;

&lt;p&gt;The extension was built. The code compiled. But iOS never triggered it. Cursor could not figure out why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT and Claude in the Browser (Dec 2025)&lt;/strong&gt;&lt;br&gt;
After Cursor hit a wall, I tried a different approach. I copied my Swift files into ChatGPT and Claude, explained the problem, and asked for solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they did well:&lt;/strong&gt;&lt;br&gt;
Both were better than Cursor at explaining iOS architecture. They understood the privacy model, the concept of opaque tokens, why Apple designed the API the way they did. If I needed to learn why something worked a certain way, ChatGPT and Claude were helpful teachers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where they broke down:&lt;/strong&gt;&lt;br&gt;
Three specific failures that cost me weeks.&lt;/p&gt;

&lt;p&gt;Problem 1: The extension never triggers.&lt;br&gt;
My DeviceActivityMonitor extension was built and embedded correctly. But iOS never called it. I wrote up the issue in detail:&lt;/p&gt;

&lt;p&gt;▎ "DeviceActivityReport extensions are NOT automatically triggered by iOS. There is no public API to&lt;br&gt;
  request a DeviceActivityReport from the main app. The extension only runs for custom contexts."&lt;/p&gt;

&lt;p&gt;ChatGPT suggested checking build phases and embedding settings. Claude suggested verifying the extension’s &lt;code&gt;Info.plist&lt;/code&gt;. Both were reasonable debugging steps, but neither identified the actual issue, which was that I was using the wrong type of extension for what I needed. I needed a &lt;code&gt;DeviceActivityMonitor&lt;/code&gt; (which watches schedules), not a &lt;code&gt;DeviceActivityReport&lt;/code&gt; (which generates usage reports).&lt;/p&gt;

&lt;p&gt;Problem 2: App icons.&lt;/p&gt;

&lt;p&gt;I needed to show the icons of apps the user selected for blocking. The Screen Time API returns opaque &lt;code&gt;ApplicationToken&lt;/code&gt; objects. You cannot extract the app name, bundle ID, or icon from them directly. Apple designed it this way for privacy.&lt;/p&gt;

&lt;p&gt;I asked both tools how to get the icons. Both said: you cannot. They suggested that other app-blocking apps maintain a pre-built library of app icons that they fall back to.&lt;/p&gt;

&lt;p&gt;This was wrong. Apple provides a &lt;code&gt;Label&lt;/code&gt; view that renders the token's icon and name natively. Neither ChatGPT nor Claude knew about this because there are very few examples of it online.&lt;/p&gt;

&lt;p&gt;Problem 3: Auto-blocking after timer expires.&lt;/p&gt;

&lt;p&gt;This was the big one. When a user’s earned screen time runs out, Habit Doom needs to automatically re-block their apps, even if the app is closed.&lt;/p&gt;

&lt;p&gt;Both ChatGPT and Claude told me this was impossible. The token is opaque. The main app cannot run in the background indefinitely. There is no push notification trigger for re-blocking. Their conclusion: you cannot programmatically re-block apps after a timer expires.&lt;/p&gt;

&lt;p&gt;They were describing real constraints. But the conclusion was wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code (Jan 2026–Present)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In January 2026, I switched to Claude Code, Anthropic’s CLI tool that works directly in your project directory.&lt;/p&gt;

&lt;p&gt;The difference was immediate. And it came down to one thing: Claude Code reads your entire project, not just the file you paste into a chat window.&lt;/p&gt;

&lt;p&gt;Solving the extension trigger problem:&lt;/p&gt;

&lt;p&gt;Claude Code read the main app, the extension code, the entitlements files, and the &lt;code&gt;project.pbxproj&lt;/code&gt; all at once. It identified that I had set up a &lt;code&gt;DeviceActivityReport&lt;/code&gt; extension (for generating usage reports) when I actually needed a &lt;code&gt;DeviceActivityMonitor&lt;/code&gt; extension (for watching time-based schedules). It restructured the extension, updated the entitlements, and fixed the App Group configuration in one session.&lt;/p&gt;

&lt;p&gt;Solving the app icons problem:&lt;br&gt;
On January 5, 2026, exactly one week after the “failed” commit, I committed: “Fixed the locked app icon issue and removed all code for screentime report.”&lt;/p&gt;

&lt;p&gt;Claude Code found that Apple’s &lt;code&gt;Label&lt;/code&gt; view accepts an ApplicationToken and renders its icon and name. It built helper classes ( &lt;code&gt;AppIconExtractor&lt;/code&gt;, &lt;code&gt;AppIconCaptureHelper&lt;/code&gt;, &lt;code&gt;AppIconHelper&lt;/code&gt;) that capture and cache these icons for use throughout the app. Three files that ChatGPT and Claude said were impossible to write.&lt;/p&gt;

&lt;p&gt;Solving auto-blocking:&lt;br&gt;
This was Claude Code’s biggest contribution. It designed a pattern called schedule-chaining:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When the user earns screen time, start a &lt;code&gt;DeviceActivityMonitor&lt;/code&gt; schedule that ends when the time expires&lt;/li&gt;
&lt;li&gt;When the schedule ends, the &lt;code&gt;intervalDidEnd&lt;/code&gt; callback fires (in the extension, not the main app)&lt;/li&gt;
&lt;li&gt;The extension re-applies shields (blocks) to all selected apps&lt;/li&gt;
&lt;li&gt;The extension chains the next schedule for the next check&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight that the other tools missed: &lt;code&gt;DeviceActivityMonitor&lt;/code&gt; extensions are separate processes. They run independently of your main app. iOS keeps them alive. So you do not need your app to be open to re-block apps. The extension does it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// The schedule-chaining pattern for auto-blocking&lt;/span&gt;
  &lt;span class="c1"&gt;// This runs in the DeviceActivityMonitor extension — NOT the main app&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;HabitDoomMonitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DeviceActivityMonitor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;intervalDidEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DeviceActivityName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Timer expired — re-block all apps&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ManagedSettingsStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadTokensFromAppGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// Shared storage&lt;/span&gt;
          &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shield&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;applications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;

          &lt;span class="c1"&gt;// Schedule the next check&lt;/span&gt;
          &lt;span class="nf"&gt;chainNextSchedule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;chainNextSchedule&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DeviceActivityCenter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

          &lt;span class="c1"&gt;// Rotate slot names so schedules don't overwrite each other&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentSlot&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DeviceActivityName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reblockChain_&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DeviceActivitySchedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nv"&gt;intervalStart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nowComponents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
              &lt;span class="nv"&gt;intervalEnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;nextCheckComponents&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
              &lt;span class="nv"&gt;repeats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startMonitoring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;during&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond the core features, Claude Code helped debug 21 critical bugs: race conditions between extensions, midnight re-blocking failures, shield conflicts. Problems that only appear when three separate processes share mutable state.&lt;br&gt;
I am still using Claude Code today for every feature and bug fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools I Have Not Tried&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two other AI coding agents worth knowing about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAI Codex&lt;/strong&gt;&lt;br&gt;
OpenAI’s cloud-based coding agent. It reads your repository, writes code, and runs tests in a sandboxed Linux environment. The iOS limitation: it cannot compile Swift for iOS, run the simulator, or test against Apple’s frameworks. You would need to take its output and integrate it yourself. Potentially useful for pure logic (data models, algorithms, networking code), but untested for anything involving Apple’s restricted APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Gemini / Jules&lt;/strong&gt;&lt;br&gt;
Google’s Gemini powers the Jules coding agent, which focuses on async GitHub tasks like fixing bugs and opening PRs. Gemini has strong reasoning and a large context window. For iOS development, the tooling is less mature than Claude Code’s interactive CLI. Jules works on isolated tasks rather than real-time development sessions. Could be worth trying for well-defined bug fixes, but I have not tested it against Screen Time API challenges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why iOS Is Uniquely Hard for AI&lt;/strong&gt;&lt;br&gt;
If you are building a web app or a standard mobile app, most AI tools work fine. iOS with system-level APIs is a different story. Here is why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Multi-target projects. A Screen Time app is three separate programs (app + two extensions) sharing
state. Most AI tools reason about one file at a time.&lt;/li&gt;
&lt;li&gt;Opaque APIs. Apple hides information deliberately for privacy. Tokens do not contain human-readable
data. The "obvious" solution (extract the app name from the token) is intentionally impossible.&lt;/li&gt;
&lt;li&gt;Almost no documentation. The Screen Time API has sparse official docs and very few community examples.
AI models trained on Stack Overflow have almost nothing to learn from.&lt;/li&gt;
&lt;li&gt;Silent failures. One wrong entitlement and the app compiles, installs, and runs, but the extension
never triggers. No error. No crash. Just silence.&lt;/li&gt;
&lt;li&gt;Extension lifecycle. Extensions are separate processes that iOS can kill and restart at any time. Code
that works when the app is open can fail completely in the background.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What I Recommend&lt;/p&gt;

&lt;p&gt;Here is my honest recommendation for building an iOS app with AI in 2026:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwljzatyceibcm6aq55v3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwljzatyceibcm6aq55v3.png" alt="table describing my recommendations" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If your app uses standard iOS APIs (UIKit, SwiftUI, Core Data, networking), Cursor and ChatGPT will get you far. But the moment you touch Apple’s restricted frameworks (Screen Time, HealthKit, FamilyControls, DeviceActivity), you need a tool that understands your entire project, not just the file you are looking at.&lt;br&gt;
For me, that tool is Claude Code. Two AI agents told me the core feature of my app was impossible. Claude Code shipped it.&lt;/p&gt;

&lt;p&gt;Habit Doom is live on the App Store. The auto-blocking works. The app icons display correctly. All because&lt;br&gt;
   one AI agent understood what the others could not.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>ai</category>
      <category>agents</category>
      <category>swift</category>
    </item>
    <item>
      <title>I Shipped 4 Failed iOS Apps. Then One Finally Worked. OR DID IT?</title>
      <dc:creator>Richard Andrews</dc:creator>
      <pubDate>Wed, 18 Mar 2026 21:00:55 +0000</pubDate>
      <link>https://dev.to/the_unmanaged_boy/i-shipped-4-failed-apps-before-one-finally-worked-heres-what-changed-3n87</link>
      <guid>https://dev.to/the_unmanaged_boy/i-shipped-4-failed-apps-before-one-finally-worked-heres-what-changed-3n87</guid>
      <description>&lt;p&gt;Open my App Store Connect right now and you will see a graveyard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UnManaged Tasks:&lt;/strong&gt; a task management app. Still live. Zero traction. &lt;strong&gt;One Diary:&lt;/strong&gt; a journaling app. Shipped it, nobody cared. &lt;strong&gt;Artizan App:&lt;/strong&gt; I do not even remember what this one did. It says “Removed from App Store.” That about sums it up.&lt;/p&gt;

&lt;p&gt;Before Habit Doom, I had built close to a dozen web and mobile apps. Every single one followed the same pattern: exciting idea, burst of energy, weeks of building, launch into silence, slow death. Some had a brief spark (a few interested testers, a moment of validation), but I could never keep the fire alive.&lt;/p&gt;

&lt;p&gt;. . .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 failed apps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Shipped before one finally worked&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;. . .&lt;/p&gt;

&lt;p&gt;I was, by every objective measure, a serial failure at shipping products.&lt;/p&gt;

&lt;p&gt;This is a story about what changed. Not because I suddenly became more talented. Not because I had a breakthrough idea that was obviously better. But because I changed how I thought about failure itself. That shift, backed by decades of research from psychologist Carol Dweck, is the reason Habit Doom exists today.&lt;/p&gt;

&lt;p&gt;This is Part 2 of a series on the science behind Habit Doom, inspired by Professor Jiang’s lecture on the PredictiveHistory YouTube channel. &lt;a href="https://habitdoom.com/blog/marshmallow-test-delayed-gratification" rel="noopener noreferrer"&gt;Part 1 covers the Marshmallow Test and delayed gratification.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;## The Research: Why Most People Quit&lt;/p&gt;

&lt;p&gt;In the 1990s, Stanford psychologist Carol Dweck conducted a series of experiments that fundamentally changed how we understand achievement. She gave children a set of puzzles, easy ones at first, then progressively harder ones designed to be unsolvable.&lt;/p&gt;

&lt;p&gt;What she observed was not about intelligence. It was about &lt;strong&gt;how the children responded to failure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One group, when they hit the impossible puzzles, said things like: “I’m not smart enough.” “I can’t do this.” They became frustrated, gave up quickly, and (here is the striking part) &lt;strong&gt;their performance on the easy puzzles dropped.&lt;/strong&gt; Failure on the hard problems made them worse at problems they had already solved.&lt;/p&gt;

&lt;p&gt;The other group responded differently. They said things like: “This is tricky, I need a new strategy.” “I love a challenge.” They leaned in. They tried different approaches. Some of them actually improved their performance even though the puzzles were designed to be unsolvable.&lt;/p&gt;

&lt;p&gt;Dweck identified two distinct mindsets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed mindset:&lt;/strong&gt; "My abilities are set. Failure means I've hit my ceiling. If I have to try hard, it
means I'm not naturally good at this."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Growth mindset:&lt;/strong&gt; "My abilities can be developed. Failure is information. Effort is the path to
mastery."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implications were massive. Dweck tracked students, athletes, and professionals across decades. The pattern held everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Students with a growth mindset &lt;strong&gt;recovered from bad grades faster&lt;/strong&gt; and ended up with higher GPAs&lt;/li&gt;
&lt;li&gt;Athletes who believed skill was trainable &lt;strong&gt;practiced more deliberately&lt;/strong&gt; and improved faster&lt;/li&gt;
&lt;li&gt;Professionals with a growth mindset &lt;strong&gt;sought feedback&lt;/strong&gt; instead of avoiding it, and advanced further in
their careers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical insight was this: &lt;strong&gt;mindset is not about positive thinking.&lt;/strong&gt; It is about how you interpret failure. Fixed mindset treats failure as evidence of who you are. Growth mindset treats failure as evidence of what to try next.&lt;/p&gt;

&lt;p&gt;## My Fixed Mindset Years&lt;/p&gt;

&lt;p&gt;I did not know about Dweck’s research when I was shipping my first apps. But looking back, I was a textbook case of fixed mindset in action.&lt;/p&gt;

&lt;p&gt;Here is what my inner monologue sounded like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;After UnManaged Tasks got zero traction:&lt;/strong&gt; "Maybe I'm just not good at product design. Real founders
know what people want. I clearly don't."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After Artizan App died:&lt;/strong&gt; "I should stick to my day job. Building products is for people who have the
instinct for it."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;After the fifth or sixth failure:&lt;/strong&gt; "I've tried enough times. If I were going to succeed at this, it
would have happened by now."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every failure felt like a verdict. Not on the product, but on me. Each abandoned project reinforced the belief that I was not the kind of person who ships successful products. That I lacked something fundamental that real founders have.&lt;/p&gt;

&lt;p&gt;And because I believed that, I acted accordingly. I stopped doing user research. I stopped iterating past the first version. I would build what I thought was good, ship it, and when people did not care, I took it as confirmation that the problem was me, not the approach. Why iterate on something if the underlying issue is that you are not talented enough?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fun2kps6uq37la76twzmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fun2kps6uq37la76twzmc.png" alt="Download trends showing 0 for all apps except Habit Doom" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That screenshot is real. That is my actual App Store Connect. Zero units. Zero proceeds. “Removed from App Store.” A graveyard of ideas I believed in and then abandoned because I interpreted silence as a final answer.&lt;/p&gt;

&lt;p&gt;## The Moment Everything Shifted&lt;/p&gt;

&lt;p&gt;Habit Doom almost died the same way.&lt;/p&gt;

&lt;p&gt;The first version of the app was, honestly, bad. Not technically broken (it worked). But the onboarding was confusing. The core concept, lock your apps until you do your habits, made perfect sense to me but was nearly incomprehensible to people seeing it for the first time.&lt;/p&gt;

&lt;p&gt;I shared it with friends, family, anyone who would look at it. The feedback was brutal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"I don't understand what this does."&lt;/li&gt;
&lt;li&gt;"Why would I want to lock my own apps?"&lt;/li&gt;
&lt;li&gt;"I would never pay for this."&lt;/li&gt;
&lt;li&gt;"The onboarding lost me after the second screen."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;They were right.&lt;/strong&gt; Every single piece of feedback was valid. The app was not clear. The value proposition was not obvious. The onboarding assumed too much context.&lt;/p&gt;

&lt;p&gt;Here is where the old me would have quit. In fact, I felt the pull. That familiar voice: “See? Another one. People don’t want this. Move on to the next idea.”&lt;/p&gt;

&lt;p&gt;But something was different this time. I am not sure I can pinpoint exactly why — maybe it was the accumulated frustration of too many abandoned projects, maybe I was just tired of the pattern. But instead of interpreting the feedback as “this idea is bad and you should stop,” I heard it as “ this execution needs work and you need to fix it.”&lt;/p&gt;

&lt;p&gt;That distinction, between “I failed” and “this approach failed,” is exactly what Dweck’s research describes. And it changed everything.&lt;/p&gt;

&lt;p&gt;## Iteration as a Mindset Practice&lt;/p&gt;

&lt;p&gt;I threw out the entire onboarding and rebuilt it from scratch. Not a tweak. A complete rethink of how a new user encounters the app for the first time.&lt;/p&gt;

&lt;p&gt;Then I tested it again. Different people this time, because the original testers already knew the concept and could not represent new users anymore. Fresh eyes. Honest reactions.&lt;/p&gt;

&lt;p&gt;It was better. But not good enough. So I rebuilt it again.&lt;/p&gt;

&lt;p&gt;And again.&lt;/p&gt;

&lt;p&gt;I went through so many rounds of this that I literally exhausted my inner circle. Every friend, every family member, every colleague, every friend-of-a-friend. They had all seen the app at some point during testing. I even recruited my girlfriend as an unpaid market research department, which mostly meant she had to casually bring up “so how do you feel about your screen time?” to every friend she met for coffee. She was thrilled (or at least took enough pity on my distressed face to pretend she was). I had no one left within two degrees of separation who had not been a test user.&lt;/p&gt;

&lt;p&gt;And that was actually the moment I knew I was ready. Not because the app was perfect — it was not. But because I had iterated so many times, with so many different people, incorporating so much real feedback, that the only way to continue improving was to show it to the world.&lt;/p&gt;

&lt;p&gt;Here is what the growth mindset actually looks like in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build something,&lt;/strong&gt; knowing it will be imperfect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show it to people,&lt;/strong&gt; not to get validation, but to get data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listen without defending.&lt;/strong&gt; Their confusion is your product's problem, not their problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuild based on what you learned,&lt;/strong&gt; not what you assumed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repeat with new people,&lt;/strong&gt; because previous testers cannot unsee what they have seen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That loop is the entire game. And the only thing that makes it possible is believing that iteration leads somewhere, that you are not just rearranging deck chairs on a sinking ship, but actually building something better with each cycle.&lt;/p&gt;

&lt;p&gt;## Why Habits Are Growth Mindset Training&lt;/p&gt;

&lt;p&gt;Here is the connection that Professor Jiang's lecture made click for me: &lt;strong&gt;daily habits are the most practical growth mindset training available.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about what happens when you commit to a daily habit:&lt;/p&gt;

&lt;p&gt;Day 1: You are terrible at it. Your guitar playing sounds awful. Your writing is stiff. Your meditation lasts 90 seconds before your mind wanders.&lt;/p&gt;

&lt;p&gt;Day 7: Slightly less terrible. You can almost play a chord change without pausing. Your writing flows a bit. You can sit for 3 minutes.&lt;/p&gt;

&lt;p&gt;Day 30: You can hear the difference. Other people can hear the difference. The thing you were bad at is becoming the thing you do.&lt;/p&gt;

&lt;p&gt;Every single day of that progression is a micro-dose of growth mindset. You are proving to yourself, with evidence you can see and hear and feel, that ability is not fixed. That effort produces results. That the person you were on Day 1 is not the person you have to be on Day 30.&lt;/p&gt;

&lt;p&gt;This is not abstract psychology. This is literally what happens when you &lt;a href="https://habitdoom.com/blog/how-habit-doom-works" rel="noopener noreferrer"&gt;track your habits in an app&lt;/a&gt; and watch the numbers climb. My guitar practice went from 0 to 23 check-ins. My reading streak hit 11 days. My &lt;a href="https://habitdoom.com/blog/marshmallow-test-delayed-gratification" rel="noopener noreferrer"&gt;habit completion rate climbed to 82%.&lt;/a&gt; Each of those numbers is concrete proof that I am not the same person I was a month ago.&lt;/p&gt;

&lt;p&gt;. . .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10% → 85%&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Habit completion improvement in 4 weeks&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;. . .&lt;/p&gt;

&lt;p&gt;And that proof compounds. The more evidence you accumulate that you can improve, the easier it becomes to face the next failure with a growth mindset instead of a fixed one. Because you have data. Not hope — data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apps.apple.com/in/app/habit-doom-anti-doomscroll/id6757255783&amp;lt;br&amp;gt;%0AHabit%20Doom:%20Anti-DoomScroll" rel="noopener noreferrer"&gt;Download Habit Doom For Free&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;## The Part Nobody Talks About: Zero Downloads Is Not the End&lt;/p&gt;

&lt;p&gt;I want to be transparent about where Habit Doom is right now, because I think the honest version of this story matters more than the polished version.&lt;/p&gt;

&lt;p&gt;As I write this, Habit Doom has 36 downloads over two weeks. No revenue — in-app purchases are not even integrated yet. The App Store trend chart shows days with zero downloads. The proceeds chart is a flat line at $0.00.&lt;/p&gt;

&lt;p&gt;By every conventional startup metric, this is not a success story. It is a story in progress. I am writing blog posts at midnight after my day job. I am testing the app on strangers because I have used up everyone I know. I am staring at download numbers that would make most people quit.&lt;/p&gt;

&lt;p&gt;But here is what is different from every other project I have shipped: &lt;strong&gt;I am not interpreting the numbers as a verdict.&lt;/strong&gt; Zero downloads this week does not mean the app is bad. It means nobody knows about it yet. Low engagement does not mean the concept fails. It means the onboarding might need another pass, or the messaging is not clear enough, or I have not found my audience yet.&lt;/p&gt;

&lt;p&gt;Every one of those is a solvable problem. A growth mindset problem. Not a “you are not cut out for this” problem.&lt;/p&gt;

&lt;p&gt;I have already iterated through more versions of this app than all my previous projects combined. I rebuilt the onboarding from scratch three times. I rewrote the App Store description. I redesigned the screenshots. I started this blog. Each iteration makes the product slightly better and my understanding slightly deeper.&lt;/p&gt;

&lt;p&gt;Is this going to work? I genuinely do not know. But for the first time in a dozen projects, I am still here. Still iterating. Still showing up. And that, according to Dweck’s research, is the only variable that actually matters.&lt;/p&gt;

&lt;p&gt;## How to Apply This to Your Own Habits&lt;/p&gt;

&lt;p&gt;You do not need to build an app to benefit from growth mindset. You just need to change how you talk to yourself when things go wrong. Here is the practical framework:&lt;br&gt;
  ### Reframe the Identity Statement&lt;/p&gt;

&lt;p&gt;Fixed: "I'm not a morning person" Growth: "I haven't found a morning routine that works for me yet"&lt;/p&gt;

&lt;p&gt;Fixed: "I can't focus for more than 10 minutes" Growth: "My focus muscle needs training — I'll start with 5 minutes"&lt;/p&gt;

&lt;p&gt;Fixed: "I always fail at habits" Growth: "My previous systems didn't work — I need a different approach"&lt;/p&gt;

&lt;p&gt;Fixed: "I'm addicted to my phone" Growth: "My environment makes it easy to scroll — I need to change the environment"&lt;/p&gt;

&lt;p&gt;The word "yet" is the most powerful word in the growth mindset vocabulary. "I can't do this yet" is a fundamentally different statement from "I can't do this." One is a status update. The other is a life sentence.&lt;/p&gt;

&lt;p&gt;### Build the Evidence Base&lt;/p&gt;

&lt;p&gt;Start tracking something. Anything. The reason apps like &lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; work is not the lock mechanic (though that helps). It is the &lt;strong&gt;visible evidence of growth.&lt;/strong&gt;  When you can see your streak climbing, your check-ins accumulating, your completion rate rising, you are building an evidence base against the fixed mindset voice.&lt;/p&gt;

&lt;p&gt;That voice says "you can't change." Your habit data says "you already have."&lt;/p&gt;

&lt;p&gt;### Expect the Dip and Plan for It&lt;/p&gt;

&lt;p&gt;Every habit has a dip. The excitement of Day 1 fades. The streak breaks. You miss a day and the fixed mindset voice screams: "See? I told you. You always quit."&lt;/p&gt;

&lt;p&gt;Plan for it in advance. Decide now what you will do when you miss a day. Not if — when. The growth mindset response is not "I failed." It is "I missed one day. My 14-day streak becomes a 1-day streak. Time to build the next one."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://habitdoom.com/blog/how-habit-doom-works" rel="noopener noreferrer"&gt;Habit Doom's streak system&lt;/a&gt; is designed around this. When you break a streak, the counter resets — but your total check-ins and earned screen time stay. You can see that even with broken streaks, the overall trajectory is upward. That is growth mindset built into the data model.&lt;/p&gt;

&lt;p&gt;## Your Mindset Is a Habit Too&lt;/p&gt;

&lt;p&gt;Carol Dweck's most important finding was not that growth mindset is better. It was that mindset itself is changeable. You are not born with a fixed or growth mindset. You practice one or the other, every day, through the stories you tell yourself about what your failures mean.&lt;/p&gt;

&lt;p&gt;Every time you miss a habit and try again the next day, you are practicing growth mindset. Every time you look at a bad week and ask "what can I change?" instead of "what is wrong with me?", you are rewiring how your brain processes failure.&lt;/p&gt;

&lt;p&gt;I shipped four failed apps before Habit Doom. Maybe Habit Doom will be the fifth failure. I genuinely do not know. But I know that I am no longer the person who interprets silence as a verdict. I am the person who interprets silence as "iterate and try again."&lt;/p&gt;

&lt;p&gt;That shift — from fixed to growth — is not something I read in a book and suddenly believed. It is something I trained, one habit at a time, one iteration at a time, one rebuilt onboarding at a time.&lt;/p&gt;

&lt;p&gt;You can train it too. Start with one habit. Track it. Watch the numbers. When the streak breaks — and it will — notice the voice that says "you always quit." Then open the app, check off the habit, and start a new streak.&lt;/p&gt;

&lt;p&gt;That is growth mindset. Not as theory. As practice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://habitdoom.com" rel="noopener noreferrer"&gt;Habit Doom&lt;/a&gt; is free on the &lt;a href="https://apps.apple.com/app/habit-doom-anti-doomscroll/id6757255783" rel="noopener noreferrer"&gt;App&lt;br&gt;
  Store&lt;/a&gt;. It takes 30 seconds to set up.&lt;br&gt;
   Your first streak starts today.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>mobile</category>
      <category>sideprojects</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
