# How We Took Biblical Battle Plans from TestFlight to the App Store

> Biblical Battle Plans is now a native iPhone app on the App Store. Why we built it, what App Review asked for, and what was hard on the way from TestFlight.

By Conner Contreras, ShirePath Solutions. Published 2026-11-09.
Source: https://shirepathsolutions.com/blog/biblical-battle-plans-on-the-app-store

Biblical Battle Plans, our Bible reading tracker built like a role-playing game, is on the App Store as a native iPhone app. Version 1.0 was approved on its first App Review submission and released on November 6, 2026. It is listed as [Biblical Battle Plans on the App Store](https://apps.apple.com/us/app/biblical-battle-plans/id6758302258), and the project's [case sheet](https://shirepathsolutions.com/work/biblical-battle-plans) has the full picture. In October we wrote about [how version two's single API and generated Swift types made the app possible](https://shirepathsolutions.com/blog/biblical-battle-plans-on-ios). This note covers the last stretch: why we built it, what App Review asked for, and what was hard on the way.

## Why we built a native app

We had two reasons.

The first was to show that we can take a native iPhone app through Apple's review, from the first commit to the store, under our own name. The App Store lists ShirePath Solutions as the seller, and the copyright line reads 2026 ShirePath Solutions LLC.

The second was that a native app is easier for readers to use than the web app installed as a progressive web app (PWA). Four things make the difference:

- **Installing.** Installing the PWA means opening the site in the browser and choosing Add to Home Screen from the share menu. The iPhone app installs from the App Store like any other app.
- **Signing in.** Readers can use Sign in with Apple, as well as Google or email.
- **Reminders.** The daily reading reminder and the streak warning come through Apple push, and a screen explains them before iOS asks for permission.
- **Getting around.** The app uses the iPhone's own navigation, with five tabs along the bottom: Home, Quests, Guilds, Chat, and Hero.

## What App Review asked for, and how it became code

Apple's review guidelines set conditions for any app where people hold accounts and write to each other. We treated each one as a requirement and built it before we submitted.

**Reporting and blocking.** Guideline 1.2 asks an app with user content to let people report it and block abusive users. In the app, a message in guild chat, a guild member, and a guild can each be reported, and any member can be blocked. A reported or blocked message disappears for that reader at once. Settings lists Blocked Users and has a Contact Support link. On the server, chat, guild names, and display names are checked against a filter of slurs and explicit terms, new readers accept the Terms of Service before they can post, and an account that breaks the Terms can be suspended. The web app got the same reporting and blocking screens, so both apps follow one set of rules.

**Account deletion.** Guideline 5.1.1(v) asks that an account made in an app can be deleted from inside it. The Hero tab has Delete Account. It calls `DELETE /api/users/account`, which removes the account and its data, and for an account that used Sign in with Apple, the server then revokes Apple's token.

**Sign in with Apple.** The sign-in screen offers it beside Google and email.

**Legal pages the store can link to.** The Terms, Privacy, and Acknowledgements pages name ShirePath Solutions LLC, and the Terms are governed by Tennessee law. The Acknowledgements page also credits the ESV text, which is used by permission of Crossway and served from our own server.

**Crash reports and a privacy label that matches the app.** Crash reports come in through Apple's MetricKit and go to the app's own `/api/errors` endpoint. The app ships a privacy manifest, and the store's privacy label lists 10 data types, all linked to the account, none used for tracking, and all used for the app's own functions. A one-line check compares the label in our listing document with the manifest, so a change to one has to be made in the other.

**Accessibility and polish.** Before submitting, we fixed form focus and a set of smaller issues, made the text larger on screens meant for reading, and raised contrast to 4.5:1 on both the web and iPhone. The app also got a new pixel-art icon, rendered from code by `pnpm ios:icon`, with a test that fails if the image changes.

We skipped external TestFlight on purpose. Version 1.0 went from our internal TestFlight group straight to App Review.

## The review prep we automated

A review goes better when the reviewer finds what they need on the first try and every field in App Store Connect is right. We automated the parts that would otherwise be done by hand for each submission.

- **One listing document.** Everything typed into App Store Connect lives in `docs/APP_STORE.md` in the repo: the name, subtitle, description, keywords, categories, privacy label, and the notes to the reviewer. A length check covers the subtitle, promotional text, keywords, description, and review notes, and the label check runs against the manifest.
- **A demo account that builds itself.** `pnpm review:seed` creates the reviewer's demo account and a companion account, with a 10-day streak, a guild called Review Company, and chat messages that include a prayer request. It is safe to run twice: a second run writes only what is missing.
- **Screenshots from a test.** A UI test drives the simulator through the six store screenshots, and `pnpm ios:screenshots` runs it.
- **A release script that checks before it uploads.** `pnpm ios:release` archives and signs the build, then refuses to upload it unless it carries the production push entitlement, the universal link for the www domain, and the Sign in with Apple entitlement. After the upload, it tags the commit as `ios-v<version>-b<build>`.
- **CI on the native side.** A macOS job regenerates the Xcode project with XcodeGen and fails on any difference, builds and runs the unit tests on a simulator, and compiles the Release configuration. A Linux job regenerates the Swift types from the web's TypeScript and fails if they changed.

The server has 86 API route files. The Swift generated from the web's TypeScript fills six files, 2,123 lines in all: the API's types and requests, plus the level table, the backdrops, the Bible books, and the legal constants. The drift checks are what keep the two sides in agreement as both apps change.

## What was hard

### The upload role

In October we wrote that the release script signs with an Account Holder or Admin account signed in to Xcode, because our App Store Connect API key had the App Manager role, which cannot use cloud-managed distribution signing. That still holds for signing. From build 111, Xcode's own upload began failing as well, with a 403 `ROLE_NOT_VALID` error.

The fix split the two jobs. The account signed in to Xcode still archives and signs the build. The script then uploads the checked file with `xcrun altool` and the App Manager API key, which it fetches from 1Password for the run and deletes when it exits. This updates the release paragraph in the October note: signing works the way that note described, and uploading now goes through `altool`.

### Removing the half-wired offline queue

The iPhone app had an offline queue, backed by a SwiftData store, meant to hold writes made without a connection and replay them later. It was never wired up end to end. In October, before review, we deleted the queue and its SwiftData store.

Now a write that gets no response rolls back, and the app says so plainly: "You appear to be offline. Check your connection and try again." Other errors read as plain sentences too, using the server's own one-line message where it has one. The web app still works offline and syncs when it reconnects. The iPhone app has no offline mode in 1.0.

### Folding levels 21 to 30 into 1.0

Version 1.0 had never been submitted, so on November 4 we put the next round of game work into it: levels 21 to 30, new tier art, animated hero backdrops, and three progression moments (a tier promotion overlay, a level-up toast, and an XP float). That brought the game to 30 levels across seven tiers of hero.

Build 174 was uploaded on November 4 and passed its checks on devices the same day, then went to App Review on November 5. The release script also refuses to run while two tiers draw from the same sprite sheet, so a tier can never ship showing another tier's art.

## Where it stands

Biblical Battle Plans 1.0.0 (build 174) has been in the US App Store since November 6. It runs on iPhone, needs iOS 26 or later, is free, and has no in-app purchases.

Version 1.0 has no iPad layout, Apple Watch app, widget, or Android version. It sends no push notifications for chat, and it has no offline mode on iPhone. The admin panel stays on the web. From the first build that ran on a real phone, on October 2, to the release took 35 days.

We build with Claude Code, Anthropic's AI coding agent. The gates in this note, from generated types and drift checks to the seeded review account and the release preflight, are what let that work move quickly while the two apps stay in step. The [case sheet](https://shirepathsolutions.com/work/biblical-battle-plans) has the drawing and the screens.
