Vibe-Coding AutoPause: A Two-Month Journey for a Two-Day App
Vibe-Coding AutoPause: A Two-Month Journey for a Two-Day App
I recently shipped a tiny macOS menu bar utility called AutoPause. It automatically pauses your music when your microphone is active - perfect for people who jump into calls and forget to mute Spotify. Getting it onto the App Store took about two calendar months from idea to release.
To be clear, this wasn't a full-time endeavor. I probably spent one to two hours a day on average, and not even consistently. I got sidetracked halfway through with a second project, so my best estimate is around 50 hours total.
It took a lot more time than I thought, and there were a couple of reasons for that.
“This Will Take Two Days” Syndrome
When I started, I honestly thought I could bash it out in a weekend. The scope was tiny, the idea was simple, and AI tools were good enough that I figured I’d just prompt my way through it.
Instead, it dragged out over weeks. Part of that was on me: I’d go down a path, realize it was the wrong abstraction or the wrong architecture, and then have to undo and rebuild parts I had already finished. A lot of time was lost to rework.
AI-First Development: Blessing and Curse
One of my goals was to build the entire thing using AI tools as much as possible. I’d estimate that 95% of the app was built through prompting - sometimes with high-level goals, sometimes with low-level refactoring requests. I wasn’t copy-pasting code blindly; I was guiding it, but still relying heavily on what the LLM gave me.
Initially, this felt like cheating. I’d just say what I wanted and code appeared. But over time, the costs piled up:
- State duplication: Multiple parts of the app managing similar state independently. Easy to create, hard to debug.
- Tight coupling: Components deeply interwoven with no clear boundaries, making testing painful.
- Maintenance hell: Fixing bugs became exponentially harder once the system got just slightly more complex.
Eventually, I had to slow down and spend real engineering effort to clean up the architecture and pay back the tech debt that had quietly accumulated.
I Had Never Done macOS Dev Before
This was my first macOS app. I had no intuition for what the idiomatic tooling looked like. I defaulted to what would let me keep using AI tools fluidly: Swift Package Manager.
Using SPM meant I could stay inside Cursor (the AI-powered IDE I was using) and iterate quickly without touching the mess of Xcode UI toggles and project files. That worked for a while - but then I hit reality.
macOS apps that are going to the App Store, and especially those using StoreKit for in-app purchases, basically require Xcode. I resisted it for as long as I could. Eventually, I ended up in a horrible hybrid setup: SPM for core app logic, Xcode for anything StoreKit-related. Two parallel build systems. Don’t do this.
Eventually, I gave up and migrated the entire thing into a proper Xcode project. That meant resetting a lot of the project configuration from scratch - not the app logic itself, but everything around it. That migration chewed up several more days of work.
App Store Hell
Another big time sink: App Store distribution. I had to convert my Apple Developer account from individual to organization, which I thought would take a day. It took more than a week. Maybe two even.
Then came the review process. I got rejected six times. Each time, a small metadata tweak or capability clarification set me back a day or two. I had underestimated how long the bureaucracy around shipping a macOS app would take. If I had known, I would have started the account conversion and App Store boilerplate work much earlier.
Solving a Real Personal Annoyance
The idea for AutoPause came from a real friction point in my own routine. I use voice dictation tools a lot, and I’d often find myself pausing and unpausing music manually every time I wanted to say something. As an engineering manager, I also spend a lot of my day in online meetings. I usually listen to music while working - so needing to manually pause and resume it around calls became an annoyance I just wanted to eliminate.
That’s the origin story: I built this to scratch my own itch.
App Architecture
AutoPause is a macOS menu bar app that watches for microphone activity. When it detects you're speaking - on a call, using dictation, whatever - it pauses your music. When your mic goes quiet for a few seconds, it resumes playback.
There are two types of media players it supports:
- Native macOS apps: Specifically Spotify and Apple Music.
- Browser-based players: YouTube, SoundCloud, etc., running in Chrome.
For native apps, I control playback using AppleScript. It's ugly, but it works.
For browser-based players, I built a Chrome extension (also fully generated using LLMs). The extension continuously reports which media sites are currently playing audio. When AutoPause detects mic input, it sends a pause command to the extension; when mic input stops, it sends a resume command.
Observability Saved Me
Because I was vibe coding my way through this project, having a way to manually test every piece became essential. I hid a Developer Menu inside the app that gave me buttons like:
- Pause Spotify
- Resume Spotify
- Pause Apple Music
- Play Apple Music
- Pause Chrome
- Play Chrome
This menu became priceless. It allowed me to QA each sub-component manually and also gave me better feedback to pass into the LLMs for debugging and iteration.
The Protocol Mismatch I Didn’t See Coming
While writing this post, I realized something I hadn’t seen during development: the control models between native apps and browser tabs were inconsistent.
For native apps, I used a "check if playing, then pause and report" approach.
For Chrome media tabs, the extension pushed state continuously to the macOS app, and the app had to manage specific tabs directly.
This was a subtle architectural divergence I hadn’t noticed until late. Once I did, I refactored the Chrome extension and WebSocket protocol to behave like the native media app control - aligning both models under a single mental model.
This is another area where more research and up-front thinking - probably done together with LLMs - could’ve paid dividends. I rushed. Because it's fun to build. Because “how hard can it be?” To counter argue my own point here though: Somtimes you learn by doing the wrong thing and paying the price to fix it.
Shiny Object Syndrome
Briefly after starting AutoPause, I got high on all the fast progress I was making. The LLMs were cooperating and I felt unstoppable. So I started a second app in parallel.
Some days I was literally double-fisting two Cursor windows, coding two apps at once.
Eventually, I realized I needed to close the loop on AutoPause before continuing. It was the simpler of the two, and finishing it gave me not just closure, but a massive pile of learnings - Xcode, macOS best practices, App Store logistics, testability, and more.
Had I waited before starting the second project, I could have used all of those lessons upfront instead of backporting them later.
Final Thoughts
AutoPause is a small utility. You'd think it’d be a two-day build.
But assumptions compounded:
→ Tooling friction
→ AI-induced tech debt
→ Platform-specific quirks
→ Chrome’s extension model
→ Mismatched architecture
→ App Store bureaucracy
→ Lack of up-front planning
→ Context-switching between side projects
All told, about 50 hours of effort spread across two months.
Was it worth it? Absolutely. I shipped. I learned. I saw how far AI coding assistants can go - and where they fall short, and I'm sure I'll take my learnings with by both into future "manual work" that I will do, as well as any AI-assisted work.
And who knows, maybe next time I'll even be a bit more willing to..... pause and think before hitting “play”.