Aaron Hoffman · Design-led AI Product Builder
Case Study · First-Run Experience
No account. No sign-up. Every step skippable.
The first-run experience for Steadcast, a native macOS podcast app live in the Mac App Store. In six screens it requests system permissions, migrates a library out of a competitor's app, personalizes discovery, and hands off. Designed and built solo, and shipped through App Store review.
The Job
Steadcast is a podcast app built around learning: it transcribes episodes on your Mac, generates summaries with Apple Intelligence, and lets one keystroke save a moment with its timestamp and context. Onboarding has to get someone from a cold launch to that, without an account or a credit card.
Most onboarding is a slideshow. This one has to do actual work: ask for system permissions, import an existing library out of Apple Podcasts or Overcast, personalize a discovery engine, and route the user to the right screen based on what they did.
The interesting decisions are not what the six screens include. They are what got taken out.
The shipped flow. Solid boxes shipped; red dashed ones were specced or built, then taken out. Every middle step can be skipped, and the final screen routes by what you actually did.
The Walkthrough
01 · Welcome
Wordmark, tagline, and three short paragraphs: why the app exists, what it does differently, and who built it. It closes on a line that does the positioning work in one sentence: no venture capital, no ads, no tracking.
There is no Skip button here, because there is nothing to skip. One button, one job. The contact address is a live link that opens the user's real mail client, not a string you have to copy.
The spec called for the 64pt app icon at the top. The wordmark shipped instead, with new SVG assets added the same day to support it. The window grew too, from the specced 640x520 to 640x680, because the manifesto needed the room.
The contact line hid a real bug. SwiftUI reads a bare string as a LocalizedStringKey, which mangled the email address on render. It ships wrapped in Text(verbatim:), and the tap opens a native compose window through NSSharingService rather than firing a mailto link.
02 · Permissions
Three cards, each explaining the reason before the ask. But the most important thing on this screen is what is missing from it.
The spec called for an Accessibility permission card, with a System Settings deep link and thirty seconds of polling. During the build, the global hotkeys turned out to use a Carbon API that does not require Accessibility at all. So the card was deleted and replaced with one reassuring line: control playback from anywhere, no permissions needed. That removed the scariest moment in the flow. A comment in the shipped code records why, so nobody adds it back.
The deletion was not a design review. It was a side effect of unrelated work. A week after onboarding shipped, the Save Memory global shortcut got built on Carbon's RegisterEventHotKey, which needs no Accessibility grant. The card came out in that same commit.
A Notifications card took the empty slot, because the new shortcut needed a way to confirm a save had happened. The permission count did not change. The size of the ask did.
03 · Your Podcasts
Exact export instructions for the four apps people actually arrive from. Three of them get a menu path. Spotify gets the truth: not supported, search for shows instead.
That line is the whole philosophy in six words. It would have been easy to omit Spotify and let people find the dead end themselves. Naming it costs nothing and buys trust.
Import runs inline, never in a modal. The button becomes a progress row, then a result line, with per-feed detail behind a disclosure that only matters if something failed. Continue stays enabled the entire time. A partial failure is never a blocker.
The privacy block is deliberately not in a card. Cards read as features, and the argument was that this is the baseline rather than a selling point.
The voice took two passes to settle. An early revision changed every we and our to I and my across the entire flow. The submission pass moved this one screen back: my servers became our servers. Singular when it is a person talking, plural when it is the product making a promise.
04 · Intelligence
Three capabilities, and a requirement note that changes based on the machine it is running on. Supported hardware sees what to enable. Unsupported hardware is told plainly that these features arrive with different hardware, rather than being sold something it cannot run.
The original copy said Apple Intelligence transcribes the episodes. It does not. Transcription runs on Apple's Speech framework; Apple Intelligence writes the summaries. That got corrected before submission. Nobody outside the project would ever have caught it.
The spec also called for a twenty-second demo video here. It was cut rather than shipped as a grey placeholder box. A screen that is honestly quiet beats one that is visibly unfinished.
The demo video died in two stages. The first revision pulled the placeholder off the final screen. The submission pass pulled it off this one. A grey box reading coming soon would have been worse than shipping neither.
That same pass renamed AI Notes to Summaries and corrected the transcription claim. One change for accuracy, one for plain language.
The same promise in the shipped app: status badges, a generated overview, and four memories saved at the exact second they happened.
05 · Interests
Ten topics in a grid. The footer line rewrites itself as the count changes, from an invitation at zero to a confirmation at three or more.
An earlier version of this picker enforced a three-topic minimum and disabled the button until you complied. That minimum was removed from the view, the service layer, and the guard clauses behind it. A gate in your own onboarding is a gate on your own product.
The subtitle carries the rest: these shape Browse, but everything is still available. The screen asks for preferences without implying a cost to giving them.
Removing the three-topic minimum was not a UI edit. The disabled button came out of the view, and the two service clauses that refused fewer than three came out with it. meetsMinimumSelection still exists, but it now returns true unconditionally, with a comment noting that any count including zero is valid.
The old picker survived, in a better job. It used to open itself as a modal in Browse and stay until you satisfied it. Now it only opens when the user asks for it, from an Interests button that appears once onboarding is done. Same component, gate turned into a control.
Eleven topics exist in the code. Ten reach the screen. Society and Politics is fully defined and then deliberately held out by an exclusion set, which is a product decision rather than an oversight.
06 · You're All Set
Five keyboard shortcuts, a support address, and a send-off that reinforces the same idea a third time: subscribe to anything, learning podcasts or comedy, whatever you are into. The app has an opinion about what it is for and explicitly refuses to enforce it.
The button reads Start Listening, not Done. And the app remembers what happened: import a library and you land in the queue, pick topics and you land in Browse. The last screen is a door, not a wall.
The spec said three shortcuts, on the logic that more would overwhelm. Five shipped, because two features arrived after the spec was written and each earned a line.
The submission pass also stripped the beta language. Things might break reads as refreshing honesty in a private build and as a warning label on the App Store.
The Discipline
Every step transition persists. Close the app on screen four and you return to screen four, not screen one. Restarting a flow someone already half-finished is one of the most common onboarding failures, and it is entirely avoidable.
On launch the app checks whether a library already exists. If it does, onboarding is marked complete and skipped entirely. Nobody who already uses the app gets welcomed to it.
Every middle step can be skipped. The primary button is never disabled. Import failures do not block. Zero topics selected is a valid answer that produces a working app.
Return continues, Escape skips, Left Arrow goes back. Every control carries an accessibility label, and the progress dots announce their position, so a screen reader hears step three of six by name.
Hardware capability is checked at runtime and the copy changes accordingly. Unsupported Macs are told the truth instead of being shown a button that leads nowhere.
Finishing onboarding suppresses the older one-off permission alerts and hint banners elsewhere in the app. Nobody gets asked twice for something they already answered.
Why This Matters To You
If someone is building your first-run experience, four questions worth asking them:
Onboarding is where most of a product's abandonment happens, and it is usually the last thing anyone designs. I built this one end to end: the spec, the copy, the interface, the state machine, and the App Store submission around it.
A one-week audit, a production-grade build, or ongoing fractional product leadership. Pricing is on the page, not behind a call.
See how we can work togetheror email hello@doubleabattery.com