# Phonebook: a Storybook-style preview gallery for SwiftUI and Compose, built for agents

> Source: <https://dev.to/orelzion/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for-agents-6g9>
> Published: 2026-08-25 17:26:17+00:00

TL;DR I built [Phonebook](https://github.com/stag-build/phonebook), an open-source CLI and MCP server that turns existing Jetpack Compose `@Preview`

s and SwiftUI `#Preview`

s into a static, self-hosted HTML gallery.

Think Storybook-style component catalog, but for native mobile previews you already have in your codebase.

No SaaS account.

No extra Storybook files to maintain.

No asking designers to install Xcode, Android Studio, or your debug app just to review a loading state.

You can also see the [live gallery](https://stag-build.github.io/phonebook/) or watch the [promo video](https://youtu.be/trHbUaG784w).

Ok, ok, let's start from the beginning.

Mobile developers actually have pretty good tools for rendering UI in isolation.

If you're building Android with Jetpack Compose, you probably already have something like this:

```
@Preview(name = "Button/Enabled")
@Composable
private fun PrimaryButtonEnabledPreview() {
    PrimaryButton(
        title = "Continue",
        isEnabled = true,
        onClick = {}
    )
}

@Preview(name = "Button/Disabled")
@Composable
private fun PrimaryButtonDisabledPreview() {
    PrimaryButton(
        title = "Continue",
        isEnabled = false,
        onClick = {}
    )
}
```

And if you're building iOS with SwiftUI, you probably have something like this:

```
#Preview("Button/Enabled", traits: .sizeThatFitsLayout) {
    PrimaryButton(title: "Continue", isEnabled: true, action: {})
}

#Preview("Button/Disabled", traits: .sizeThatFitsLayout) {
    PrimaryButton(title: "Continue", isEnabled: false, action: {})
}
```

This is great when you are the developer sitting inside the IDE.

But then a designer asks:

"Can I see all the button states?"

Or a PM asks:

"What does the empty state look like now?"

Or QA asks:

"Do we have a dark mode version of this?"

And suddenly the preview is not enough.

Because the preview is trapped inside a developer workflow.

On web teams, this problem has a very familiar answer: Storybook.

You put components in stories, publish the Storybook, and now everyone has a place to see buttons, forms, empty states, loading states, weird edge cases, and all the other things that are annoying to reach through the real app.

But native mobile is different.

Brad Frost, who has written a lot about design systems, described the native tooling layer as still being immature and mentioned the recurring question: ["is there a Storybook for iOS and Android?"](https://bradfrost.com/blog/post/the-design-system-ecosystem/)

That question comes up because the pain is real, and you can see the same pattern in public write-ups from teams who already hit it.

The team at rebuy wrote about building their own ["Storybook light" for apps](https://medium.com/rebuy-recommerce/a-breeze-of-storybook-for-the-apps-366a45d9650e) after they couldn't find a suitable off-the-shelf tool. Doist wrote about building an [automated Android component catalog](https://www.doist.dev/android_component_catalog/) because their design team had trouble knowing what reusable UI existed and how implementation compared to specs.

Pavel Sharanda's [NativeBook](https://hackernoon.com/nativebook-unifying-the-native-ios-development-experience-with-storybookjs) took another run at the same idea for iOS: catalog app, previews, snapshot tests, and HTML docs from the same snippets.

So this is not a new problem.

Actually, that is part of why I wanted to build Phonebook.

When enough teams keep building their own internal version of the same thing, there is probably a missing tool hiding there.

I didn't want to invent a new way to describe mobile UI.

Developers already have previews.

They are already close to the production component.

They are already written in the same language as the app.

They already know how to render disabled buttons, error states, empty states, loading states, dark mode, and all the other tiny pieces we keep pretending are "just UI".

So my thought was:

Why not harvest those?

Not write a second Storybook-style codebase.

Not maintain a separate sample app until everyone forgets about it.

Not upload screenshots to some external service and hope the pricing still makes sense next year.

Just take the previews already in the repo and turn them into a static website.

That's Phonebook.

Phonebook has two main commands:

```
phonebook generate
phonebook build
```

`generate`

renders the native previews and writes a bundle:

```
phonebook-out/
  manifest.json
  images/
    ...
```

`build`

takes that bundle and writes a static site:

```
phonebook-out/
  index.html
  manifest.json
  images/
    ...
```

That's it.

Plain HTML, CSS, JavaScript, and image files.

You can open it from `file://`

.

You can upload it to GitHub Pages.

You can attach it as a CI artifact.

You can put it on S3, Vercel, Netlify, an internal static host, or whatever your company already uses.

Phonebook does not care.

And honestly, that is the point.

On Android, Phonebook uses:

So it can run on Linux CI without an emulator.

The rough setup looks like this:

```
// app/build.gradle.kts
plugins {
    id("io.github.takahirom.roborazzi")
}

roborazzi {
    generateComposePreviewRobolectricTests {
        enable = true
        packages = listOf("com.example.app")
    }
}

dependencies {
    testImplementation("org.robolectric:robolectric:4.14.1")
    testImplementation("io.github.takahirom.roborazzi:roborazzi:1.72.0")
    testImplementation("io.github.takahirom.roborazzi:roborazzi-compose:1.72.0")
    testImplementation("io.github.sergio-sastre.ComposablePreviewScanner:android:0.9.3")
    testImplementation("io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:1.72.0")
    testImplementation("androidx.compose.ui:ui-test-junit4")
}
```

Then add `phonebook.config.json`

:

```
{
  "appName": "My Android App",
  "platform": "android",
  "android": {
    "modules": [":app"],
    "variant": "debug"
  }
}
```

And run:

```
npx @stag-build/phonebook generate -C /path/to/your/android/repo
npx @stag-build/phonebook build -C /path/to/your/android/repo
```

On iOS, Phonebook uses [SnapshotPreviews](https://github.com/getsentry/SnapshotPreviews) through `xcodebuild test`

.

You add a small XCTest target:

``` python
import SnapshottingTests

final class PhonebookSnapshotTests: SnapshotTest {
    override class func snapshotPreviews() -> [String]? {
        return nil // record every #Preview
    }
}
```

Then add `phonebook.config.json`

next to your `.xcodeproj`

:

```
{
  "appName": "My iOS App",
  "platform": "ios",
  "ios": {
    "project": "MyApp.xcodeproj",
    "scheme": "MyApp",
    "simulator": "iPhone 17 Pro"
  }
}
```

And run:

```
npx @stag-build/phonebook generate -C /path/to/your/ios/repo
npx @stag-build/phonebook build -C /path/to/your/ios/repo
```

Open `phonebook-out/index.html`

, and there's your gallery.

Phonebook does not require a new annotation.

It reads the preview names and turns them into `component / state`

.

So this:

```
@Preview(name = "UserCard/Loading")
@Composable
private fun UserCardLoadingPreview() {
    UserCard(isLoading = true)
}
```

And this:

```
#Preview("UserCard/Loading", traits: .sizeThatFitsLayout) {
    UserCard(isLoading: true)
}
```

Both become:

`User Card`

`Loading`

If you add:

```
UserCard/Default
UserCard/Loading
UserCard/Error
UserCard/Dark
```

Phonebook groups them together as one component card with multiple states.

This sounds small.

But in practice, this is where the value is.

Because people usually don't ask, "Can you render one preview?"

They ask, "Can I see all of it?"

All the buttons.

All the badges.

All the cards.

All the empty states.

All the dark mode states.

All the weird combinations we forget to check until the release is almost out.

The grid is the product.

If you are using React Native, Storybook already has native and web options. The [React Native Storybook docs](https://storybook.js.org/docs/get-started/frameworks/react-native-web-vite) explain the tradeoff pretty clearly: native is higher fidelity because it runs in the actual app, while web is easier to publish and share.

But Phonebook is aimed at a different workflow.

It is for native Compose and SwiftUI codebases where the team already uses previews and wants a shareable artifact without creating a parallel story system.

So instead of:

```
component code
preview code
storybook story code
catalog app code
```

The goal is:

```
component code
preview code
generated gallery
```

Less ceremony.

Less drift.

Less "wait, does anyone still update this?"

Because static things are boring in the best way.

They are easy to host.

Easy to archive.

Easy to attach to CI.

Easy to send in Slack.

Easy to open on a designer's machine.

Easy to diff across builds if you want to get fancy later.

There is no Phonebook cloud.

There is no login.

There is no organization setup.

There is no "please ask your admin to approve this workspace integration" moment.

You own the output.

For a lot of mobile teams, this matters.

Not because SaaS is bad.

SaaS is great when you want a service.

But sometimes all you need is an HTML folder with screenshots of your actual components.

This is the part I care about more than I expected.

Phonebook is not only a CLI. It also ships an MCP server:

```
npx @stag-build/phonebook mcp
```

So a coding agent can help with the workflow inside your actual mobile repo.

For example, you can ask an agent:

```
Use the phonebook MCP and create a catalog for my designer.
```

And the MCP server exposes tools for things like:

The MCP server does not need to be magical.

Actually, I prefer that it isn't.

It should tell the agent what exists, what is missing, what command to run, and what generated successfully. The coding agent can then make the repo changes: add missing previews, fix naming, add the config file, or wire CI.

This feels like the right boundary to me.

Phonebook knows Phonebook.

The agent knows how to edit your repo.

For Android, a GitHub Actions job can run on Ubuntu:

```
name: phonebook-android
on: [push]

jobs:
  gallery:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '17'

      - uses: gradle/actions/setup-gradle@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npx @stag-build/phonebook generate -C .
      - run: npx @stag-build/phonebook build -C .

      - uses: actions/upload-artifact@v4
        with:
          name: phonebook-site
          path: phonebook-out
```

For iOS, use a macOS runner because SnapshotPreviews needs a simulator:

```
name: phonebook-ios
on: [push]

jobs:
  gallery:
    runs-on: macos-15
    steps:
      - uses: actions/checkout@v4

      - name: Select Xcode
        run: sudo xcode-select -s /Applications/Xcode.app

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - run: npx @stag-build/phonebook generate -C .
      - run: npx @stag-build/phonebook build -C .

      - uses: actions/upload-artifact@v4
        with:
          name: phonebook-site
          path: phonebook-out
```

You can publish the same static folder to GitHub Pages or any internal host.

Phonebook does not publish anything by itself. It just gives you the artifact.

I don't want this to become another place where teams have to describe the same UI again.

That is how tools rot.

Someone adds a component.

Someone forgets to add the story.

The design system site looks complete, but the app has moved on.

Then everyone stops trusting it.

And once people stop trusting the catalog, it becomes a museum.

Very professional.

Very nicely organized.

Completely useless.

Phonebook's bet is that previews are already close enough to the source of truth that harvesting them is better than asking teams to maintain a second catalog manually.

Will this solve every design-system problem?

Of course not.

It will not replace Figma.

It will not tell you whether the component API is good.

It will not magically clean up years of duplicated buttons.

It will not make naming conventions appear by themselves. Sadly.

But it gives you a real, shareable, desktop-friendly view of the mobile UI that already exists.

And that is a surprisingly big step.

Install it:

```
npm install -g @stag-build/phonebook
```

Or run it without installing:

```
npx @stag-build/phonebook generate -C /path/to/your/repo
npx @stag-build/phonebook build -C /path/to/your/repo
```

Or add it to your agent as an MCP server:

```
{
  "mcpServers": {
    "phonebook": {
      "command": "npx",
      "args": ["-y", "@stag-build/phonebook", "mcp"]
    }
  }
}
```

Links:

If you have a real Android or iOS repo with previews already in it, try generating a gallery from that repo and open the result with someone who does not live inside your IDE.

That's the test I care about.
