Be I/O

The AQI is X. Should you be outside?

Climate change is here

I’ve been living in Chicago for most of my life, and this was never really a thing until a couple of years ago. Since then, it’s happened every year, from fires in California, Colorado, Canada, or elsewhere. The smoke comes, sticks around for a couple of weeks, and gets better or worse based on the direction of the wind.

In late July, Chicago rapidly became enveloped in a cloud of smoke that was worse than anything I had ever experienced: the AQI was over 700 for nearly a day.

I’ve been running close to 60 miles per week, so when I woke up one morning to find my own bedroom smelling like smoke, I panicked and needed to find someplace to run. I hate running on treadmills and dislike going to gyms, but at least the decision to not run outside was easy. In the days that followed, though, as the air quality changed dramatically with the wind, I found myself spending 10–15 minutes every morning looking at as many as five apps, trying to decide whether it was yet another day when I needed to run while staring at a wall.

Why are all these apps telling me completely different things? I wondered.

So I decided to get my own sensor and trust it rather than third-party sources.

PurpleAir Zen sensors were on backorder, so I had two weeks to think about what I wanted to do once I had access to the raw data that could tell me whether to run inside or outside.

Once I got the sensor, I had a pretty solid idea for a crazy-simple app: it would take the numbers from a recent metabolic assessment I’d done—which documented the volume of air I took in alongside my heart rate—and reverse-engineer the WHO’s guidance on PM2.5 intake to decide whether a workout would push me above the limit or keep me below it.

What I didn’t know was a ton of other crazy stuff:

  • Government-managed sensors are super well calibrated but post data two to three hours late.
  • Personal sensors—like the Zen I had just bought—mistake humidity for particulates, so they require dramatic correction algorithms, sometimes halving the reported concentration.
  • PurpleAir’s API is expensive AF.
  • AirGradient has two APIs, and the sensors are identified using different values of locationId in each, with no one-to-one mapping.
  • Vibe-coding apps is intoxicating but crumbles quickly.
  • Vibe-coding apps is drowning App Store reviewers in submissions, changing the development lifecycle of an app completely.
  • Putting the last two things together resulted in this being the first side project I’ve worked on that was stressful and, frankly, not that fun—even if the result is awesome and I use it every day.

The result of all of this is Be I/O, an app I mostly vibe-coded (my first and last vibe-coded project, at least for a while).

Problems solved and problems not

It solves exactly the problem I had: when I can’t decide whether the air is good enough to run outside because it’s right on the cusp of WHO guidance, the app has enough data about me and my plan that I can trust its decision rather than waffling back and forth.

It also introduced some problems. I typically know my apps inside and out, so when I see some strange behavior, I know almost immediately what it is. And if the strange behavior is bad, I can respond quickly and ship a fix. But by vibe-coding this, I had no choice but to ask Claude what went wrong. And even if I had a fix, the ridiculously slow App Store review process—three weeks!—meant that I had little choice but to accept that there was a bug in production and that there would be one for a while.

None of my side projects make actual money, so I work on them for a combination of reasons: to solve problems that I have and because I derive immense joy from actually building things. I work mostly on iOS both because I love interacting with apps I wrote with my own fingers and because I hate managing servers. So while Be I/O was a success on those levels, I plan on scaling back my Claude use and refactoring the app until I understand it.

Fun things

There were some really fun things that came out of this, though.

API keys

I don’t ever want to manage a server again, so building apps that use authenticated APIs can be a challenge. You can’t bundle a key in the app because it’s so easy to find and use. You can’t store a key on a server and willy-nilly dish it out for the same reason. But during those two weeks when my Zen was on backorder, I had an idea so brilliant that I’ll use it again many times:

Be I/O uses Cloudflare Workers for its API. In only a few hundred lines of TypeScript, the Worker proxies requests from the app to three different authenticated APIs, injecting the required keys along the way and aggressively caching to minimize calls.

That’s not a novel idea: Cloudflare Workers have gotten super powerful, take close to zero time for cold starts, and are so cheap that, at my volume, I won’t need to worry about paying for them for a while.

But the brilliant part is how I make sure my API only serves my app: require a subscription.

Be I/O has an ultra-cheap subscription because I can use the receipt for authentication. Apple has a framework called App Attest that does something similar, but it requires storage, which is too complex for my don’t-want-to-manage-servers side projects. My API can take apart a receipt, verify that the root certificate is Apple’s, and check that it includes my bundle ID—all in 29 milliseconds. That’s not free, but it’s pretty damn close.

Cloud stuff for iOS is 100x better than it used to be

You can use GitHub Actions for CI/CD for pretty cheap! You can use Xcode Cloud for even cheaper! Holy macaroni, I’ve been having so much fun getting all my apps hooked up with Xcode Cloud for CI/CD and using GitHub Issues and Milestones to manage my planning. I started doing this because of my distrust of Claude, but ended up absolutely loving it because, Claude or not, I’m confident now about what I’m shipping and spending less time wondering what I should do next.

I love building things with my own hands

The stress I experienced when I discovered that Claude had built this app on a fundamental flaw—yadda yadda yadda, yes, I know I’m responsible for this, but a stubbed value looks like a reasonable thing—that I couldn’t fix because Apple was taking so long made me want to work on my other projects.

So while I was waiting two-plus weeks for Apple to review Be I/O, I shipped updates to four of my five published iOS apps, half a dozen or more of my web projects, and got CI/CD running in the cloud for almost all of them. Claude helped me with test coverage, for sure, and helped me validate ideas before I spent too much time banging my head against the wall. But mostly, Claude reminded me how much I love building things with my own hands, “10x productivity” or not.


Anyway, it’s been a journey, and I’m very happy with the result. And the app is good too.

#ios #air #aqi

← Return