Steadcast

Case Study · First-Run Experience

Six screens, and nothing you have to do.

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.

Role · Design and build, solo Platform · macOS, SwiftUI Status · Shipped, Mac App Store
Steadcast running on a MacBook, showing the Browse view with topic collections and a populated library

The Job

Get a stranger from launch to listening.

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.

EXISTING LIBRARY DETECTED · ONBOARDING SKIPPED ENTIRELY ACCESSIBILITY CARD the API never needed it REMOVED DEMO VIDEO cut, not placeholdered REMOVED 3-TOPIC MINIMUM gate removed REMOVED LAUNCH cold start 01 WELCOME one button, nothing to skip 02 PERMISSIONS the reason before the ask 03 IMPORT OPML in · inline, never blocks 04 INTELLIGENCE honest on unsupported Macs 05 INTERESTS zero topics is a valid answer 06 ALL SET a door, not a wall QUEUE imported a library BROWSE picked topics ESC · EVERY MIDDLE STEP SKIPPABLE · NOTHING REQUIRED

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

Six screens, one at a time.

The Steadcast welcome screen: wordmark, the tagline Podcasts for the curious, a three-paragraph manifesto, and a gold Get Started button

01 · Welcome

Say who you are before you ask for anything.

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.

Build Note

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.

The permissions screen: three cards for Notifications, Global Shortcuts, and Apple Intelligence, each with an explanation and a status

02 · Permissions

The best permission request is the one you delete.

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.

Build Note

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.

Before The earlier permissions screen: an Accessibility Permission card with a three-row shortcut table and an Allow Accessibility button, above an Apple Intelligence card
February. Two cards. The first asks you to leave the app, find Steadcast in a System Settings list, toggle it on, and come back. Its own description is too long for the card and truncates mid-sentence.
Shipped The shipped permissions screen: three compact horizontal cards for Notifications, Global Shortcuts, and Apple Intelligence
Today. Three compact rows, one real ask. The scariest request on the screen was not redesigned or reworded. It was removed, because it turned out never to have been necessary.
The import screen: export instructions for four podcast apps, a Choose OPML File button, and a privacy statement

03 · Your Podcasts

Name the limitation out loud.

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.

Build Note

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.

The intelligence screen: three feature cards for Transcribe, Summaries, and Export to MD, with a hardware requirement note

04 · Intelligence

Do not overclaim the AI.

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.

Build Note

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.

An episode in the shipped app showing Transcribed and AI Notes badges, a generated summary, and four saved memories with timestamps

The same promise in the shipped app: status badges, a generated overview, and four memories saved at the exact second they happened.

The interests screen: eleven learning domains in a three-column grid with a dynamic footer line

05 · Interests

Personalize without building a gate.

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.

Build Note

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.

The final screen: a gold checkmark, five keyboard shortcuts in monospace, and a support address

06 · You're All Set

Hand off, do not dead-end.

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.

Build Note

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

What holds it together.

Quit halfway, come back where you left off.

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.

Existing users never see it.

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.

Nothing is required.

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.

Full keyboard, full VoiceOver.

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.

It adapts to the actual machine.

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.

It cleans up after itself.

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

Your comparison point.

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.

Want a first run this considered?

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 together

or email hello@doubleabattery.com