{"slug": "phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for", "title": "Phonebook: a Storybook-style preview gallery for SwiftUI and Compose, built for agents", "summary": "A developer built Phonebook, an open-source CLI and MCP server that converts existing Jetpack Compose and SwiftUI previews into a static, self-hosted HTML gallery, offering a Storybook-style component catalog for native mobile development. The tool aims to address the lack of a native mobile equivalent to Storybook, allowing designers, PMs, and QA to view UI states without needing developer tools.", "body_md": "TL;DR I built [Phonebook](https://github.com/stag-build/phonebook), an open-source CLI and MCP server that turns existing Jetpack Compose `@Preview`\n\ns and SwiftUI `#Preview`\n\ns into a static, self-hosted HTML gallery.\n\nThink Storybook-style component catalog, but for native mobile previews you already have in your codebase.\n\nNo SaaS account.\n\nNo extra Storybook files to maintain.\n\nNo asking designers to install Xcode, Android Studio, or your debug app just to review a loading state.\n\nYou can also see the [live gallery](https://stag-build.github.io/phonebook/) or watch the [promo video](https://youtu.be/trHbUaG784w).\n\nOk, ok, let's start from the beginning.\n\nMobile developers actually have pretty good tools for rendering UI in isolation.\n\nIf you're building Android with Jetpack Compose, you probably already have something like this:\n\n```\n@Preview(name = \"Button/Enabled\")\n@Composable\nprivate fun PrimaryButtonEnabledPreview() {\n    PrimaryButton(\n        title = \"Continue\",\n        isEnabled = true,\n        onClick = {}\n    )\n}\n\n@Preview(name = \"Button/Disabled\")\n@Composable\nprivate fun PrimaryButtonDisabledPreview() {\n    PrimaryButton(\n        title = \"Continue\",\n        isEnabled = false,\n        onClick = {}\n    )\n}\n```\n\nAnd if you're building iOS with SwiftUI, you probably have something like this:\n\n```\n#Preview(\"Button/Enabled\", traits: .sizeThatFitsLayout) {\n    PrimaryButton(title: \"Continue\", isEnabled: true, action: {})\n}\n\n#Preview(\"Button/Disabled\", traits: .sizeThatFitsLayout) {\n    PrimaryButton(title: \"Continue\", isEnabled: false, action: {})\n}\n```\n\nThis is great when you are the developer sitting inside the IDE.\n\nBut then a designer asks:\n\n\"Can I see all the button states?\"\n\nOr a PM asks:\n\n\"What does the empty state look like now?\"\n\nOr QA asks:\n\n\"Do we have a dark mode version of this?\"\n\nAnd suddenly the preview is not enough.\n\nBecause the preview is trapped inside a developer workflow.\n\nOn web teams, this problem has a very familiar answer: Storybook.\n\nYou 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.\n\nBut native mobile is different.\n\nBrad 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/)\n\nThat 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.\n\nThe 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.\n\nPavel 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.\n\nSo this is not a new problem.\n\nActually, that is part of why I wanted to build Phonebook.\n\nWhen enough teams keep building their own internal version of the same thing, there is probably a missing tool hiding there.\n\nI didn't want to invent a new way to describe mobile UI.\n\nDevelopers already have previews.\n\nThey are already close to the production component.\n\nThey are already written in the same language as the app.\n\nThey 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\".\n\nSo my thought was:\n\nWhy not harvest those?\n\nNot write a second Storybook-style codebase.\n\nNot maintain a separate sample app until everyone forgets about it.\n\nNot upload screenshots to some external service and hope the pricing still makes sense next year.\n\nJust take the previews already in the repo and turn them into a static website.\n\nThat's Phonebook.\n\nPhonebook has two main commands:\n\n```\nphonebook generate\nphonebook build\n```\n\n`generate`\n\nrenders the native previews and writes a bundle:\n\n```\nphonebook-out/\n  manifest.json\n  images/\n    ...\n```\n\n`build`\n\ntakes that bundle and writes a static site:\n\n```\nphonebook-out/\n  index.html\n  manifest.json\n  images/\n    ...\n```\n\nThat's it.\n\nPlain HTML, CSS, JavaScript, and image files.\n\nYou can open it from `file://`\n\n.\n\nYou can upload it to GitHub Pages.\n\nYou can attach it as a CI artifact.\n\nYou can put it on S3, Vercel, Netlify, an internal static host, or whatever your company already uses.\n\nPhonebook does not care.\n\nAnd honestly, that is the point.\n\nOn Android, Phonebook uses:\n\nSo it can run on Linux CI without an emulator.\n\nThe rough setup looks like this:\n\n```\n// app/build.gradle.kts\nplugins {\n    id(\"io.github.takahirom.roborazzi\")\n}\n\nroborazzi {\n    generateComposePreviewRobolectricTests {\n        enable = true\n        packages = listOf(\"com.example.app\")\n    }\n}\n\ndependencies {\n    testImplementation(\"org.robolectric:robolectric:4.14.1\")\n    testImplementation(\"io.github.takahirom.roborazzi:roborazzi:1.72.0\")\n    testImplementation(\"io.github.takahirom.roborazzi:roborazzi-compose:1.72.0\")\n    testImplementation(\"io.github.sergio-sastre.ComposablePreviewScanner:android:0.9.3\")\n    testImplementation(\"io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:1.72.0\")\n    testImplementation(\"androidx.compose.ui:ui-test-junit4\")\n}\n```\n\nThen add `phonebook.config.json`\n\n:\n\n```\n{\n  \"appName\": \"My Android App\",\n  \"platform\": \"android\",\n  \"android\": {\n    \"modules\": [\":app\"],\n    \"variant\": \"debug\"\n  }\n}\n```\n\nAnd run:\n\n```\nnpx @stag-build/phonebook generate -C /path/to/your/android/repo\nnpx @stag-build/phonebook build -C /path/to/your/android/repo\n```\n\nOn iOS, Phonebook uses [SnapshotPreviews](https://github.com/getsentry/SnapshotPreviews) through `xcodebuild test`\n\n.\n\nYou add a small XCTest target:\n\n``` python\nimport SnapshottingTests\n\nfinal class PhonebookSnapshotTests: SnapshotTest {\n    override class func snapshotPreviews() -> [String]? {\n        return nil // record every #Preview\n    }\n}\n```\n\nThen add `phonebook.config.json`\n\nnext to your `.xcodeproj`\n\n:\n\n```\n{\n  \"appName\": \"My iOS App\",\n  \"platform\": \"ios\",\n  \"ios\": {\n    \"project\": \"MyApp.xcodeproj\",\n    \"scheme\": \"MyApp\",\n    \"simulator\": \"iPhone 17 Pro\"\n  }\n}\n```\n\nAnd run:\n\n```\nnpx @stag-build/phonebook generate -C /path/to/your/ios/repo\nnpx @stag-build/phonebook build -C /path/to/your/ios/repo\n```\n\nOpen `phonebook-out/index.html`\n\n, and there's your gallery.\n\nPhonebook does not require a new annotation.\n\nIt reads the preview names and turns them into `component / state`\n\n.\n\nSo this:\n\n```\n@Preview(name = \"UserCard/Loading\")\n@Composable\nprivate fun UserCardLoadingPreview() {\n    UserCard(isLoading = true)\n}\n```\n\nAnd this:\n\n```\n#Preview(\"UserCard/Loading\", traits: .sizeThatFitsLayout) {\n    UserCard(isLoading: true)\n}\n```\n\nBoth become:\n\n`User Card`\n\n`Loading`\n\nIf you add:\n\n```\nUserCard/Default\nUserCard/Loading\nUserCard/Error\nUserCard/Dark\n```\n\nPhonebook groups them together as one component card with multiple states.\n\nThis sounds small.\n\nBut in practice, this is where the value is.\n\nBecause people usually don't ask, \"Can you render one preview?\"\n\nThey ask, \"Can I see all of it?\"\n\nAll the buttons.\n\nAll the badges.\n\nAll the cards.\n\nAll the empty states.\n\nAll the dark mode states.\n\nAll the weird combinations we forget to check until the release is almost out.\n\nThe grid is the product.\n\nIf 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.\n\nBut Phonebook is aimed at a different workflow.\n\nIt is for native Compose and SwiftUI codebases where the team already uses previews and wants a shareable artifact without creating a parallel story system.\n\nSo instead of:\n\n```\ncomponent code\npreview code\nstorybook story code\ncatalog app code\n```\n\nThe goal is:\n\n```\ncomponent code\npreview code\ngenerated gallery\n```\n\nLess ceremony.\n\nLess drift.\n\nLess \"wait, does anyone still update this?\"\n\nBecause static things are boring in the best way.\n\nThey are easy to host.\n\nEasy to archive.\n\nEasy to attach to CI.\n\nEasy to send in Slack.\n\nEasy to open on a designer's machine.\n\nEasy to diff across builds if you want to get fancy later.\n\nThere is no Phonebook cloud.\n\nThere is no login.\n\nThere is no organization setup.\n\nThere is no \"please ask your admin to approve this workspace integration\" moment.\n\nYou own the output.\n\nFor a lot of mobile teams, this matters.\n\nNot because SaaS is bad.\n\nSaaS is great when you want a service.\n\nBut sometimes all you need is an HTML folder with screenshots of your actual components.\n\nThis is the part I care about more than I expected.\n\nPhonebook is not only a CLI. It also ships an MCP server:\n\n```\nnpx @stag-build/phonebook mcp\n```\n\nSo a coding agent can help with the workflow inside your actual mobile repo.\n\nFor example, you can ask an agent:\n\n```\nUse the phonebook MCP and create a catalog for my designer.\n```\n\nAnd the MCP server exposes tools for things like:\n\nThe MCP server does not need to be magical.\n\nActually, I prefer that it isn't.\n\nIt 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.\n\nThis feels like the right boundary to me.\n\nPhonebook knows Phonebook.\n\nThe agent knows how to edit your repo.\n\nFor Android, a GitHub Actions job can run on Ubuntu:\n\n```\nname: phonebook-android\non: [push]\n\njobs:\n  gallery:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-java@v4\n        with:\n          distribution: temurin\n          java-version: '17'\n\n      - uses: gradle/actions/setup-gradle@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n\n      - run: npx @stag-build/phonebook generate -C .\n      - run: npx @stag-build/phonebook build -C .\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: phonebook-site\n          path: phonebook-out\n```\n\nFor iOS, use a macOS runner because SnapshotPreviews needs a simulator:\n\n```\nname: phonebook-ios\non: [push]\n\njobs:\n  gallery:\n    runs-on: macos-15\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Select Xcode\n        run: sudo xcode-select -s /Applications/Xcode.app\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n\n      - run: npx @stag-build/phonebook generate -C .\n      - run: npx @stag-build/phonebook build -C .\n\n      - uses: actions/upload-artifact@v4\n        with:\n          name: phonebook-site\n          path: phonebook-out\n```\n\nYou can publish the same static folder to GitHub Pages or any internal host.\n\nPhonebook does not publish anything by itself. It just gives you the artifact.\n\nI don't want this to become another place where teams have to describe the same UI again.\n\nThat is how tools rot.\n\nSomeone adds a component.\n\nSomeone forgets to add the story.\n\nThe design system site looks complete, but the app has moved on.\n\nThen everyone stops trusting it.\n\nAnd once people stop trusting the catalog, it becomes a museum.\n\nVery professional.\n\nVery nicely organized.\n\nCompletely useless.\n\nPhonebook'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.\n\nWill this solve every design-system problem?\n\nOf course not.\n\nIt will not replace Figma.\n\nIt will not tell you whether the component API is good.\n\nIt will not magically clean up years of duplicated buttons.\n\nIt will not make naming conventions appear by themselves. Sadly.\n\nBut it gives you a real, shareable, desktop-friendly view of the mobile UI that already exists.\n\nAnd that is a surprisingly big step.\n\nInstall it:\n\n```\nnpm install -g @stag-build/phonebook\n```\n\nOr run it without installing:\n\n```\nnpx @stag-build/phonebook generate -C /path/to/your/repo\nnpx @stag-build/phonebook build -C /path/to/your/repo\n```\n\nOr add it to your agent as an MCP server:\n\n```\n{\n  \"mcpServers\": {\n    \"phonebook\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@stag-build/phonebook\", \"mcp\"]\n    }\n  }\n}\n```\n\nLinks:\n\nIf 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.\n\nThat's the test I care about.", "url": "https://wpnews.pro/news/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for", "canonical_source": "https://dev.to/orelzion/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for-agents-6g9", "published_at": "2026-08-25 17:26:17+00:00", "updated_at": "2026-08-25 17:45:28.460628+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Phonebook", "Jetpack Compose", "SwiftUI", "Storybook", "Brad Frost", "rebuy", "Doist", "Pavel Sharanda"], "alternates": {"html": "https://wpnews.pro/news/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for", "markdown": "https://wpnews.pro/news/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for.md", "text": "https://wpnews.pro/news/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for.txt", "jsonld": "https://wpnews.pro/news/phonebook-a-storybook-style-preview-gallery-for-swiftui-and-compose-built-for.jsonld"}}