Next steps
Next steps
What’s left between “works end-to-end on my device” and “publicly shipped on GitHub + the App Store.” Living checklist — update as items land.
Sections:
Conventions:
[ ]open,[x]done,[~]in-progress.- Items tagged external require Apple-side or github.com-UI work, not code.
- Items tagged code / doc are in-repo.
GitHub push
Blocking
- Commit the working tree. 8 commits landed: core library, iOS impls, SwiftUI screens, app shell, tests, screenshot automation, pre-launch docs, gitignore.
.gitignorecovers the risky paths. Verified:.env,.dev-secrets,Cairn.xcodeproj/,vendor/,.bundle/,fastlane/screenshots/,.ipa,.dSYM, etc.- Privacy-policy URL. GitHub Pages deployed at
https://glarue.github.io/cairn/PRIVACY. Referenced fromdocs/app-store-metadata.md.
Strongly recommended
- Embed screenshots in README. Status, Pending Review, Setup Welcome — inline at 220px.
- Repo description + topics. external. On github.com: short description + topics (
immich,ios,swift,swiftui,photos,photo-management,self-hosted,photo-sync,immich-client,apple-photos). Set via GitHub UI. - Cut
v0.1.0tag. external.CHANGELOG.mdalready has the 0.1.0 section. Onegit tag v0.1.0 && git push --tagsafter the full code push lands.
Nice-to-haves
- Branch protection on
main— require PR + CI pass. GitHub Settings → Branches. Post-push. - Enable GitHub Security advisories.
SECURITY.mdalready points at the private-advisory flow; the feature needs to be toggled on in the repo’s Security tab.
App Store submission prep
Ordered checklist — work through top to bottom.
1. Apple Developer account
- Paid Apple Developer membership. external. $99/year. Without it, no TestFlight + no App Store. Enroll at developer.apple.com/enroll. Can take up to 48 hours to process.
2. App Store Connect setup
- Create app record in App Store Connect with bundle ID
app.cairn.ios. Watch for name collision — “cairn” may already be claimed or rejected as too generic. Fallback names:cairn for Immich,cairn — Immich sync,cairn · Immich. - Create App Store Connect API key. Users & Access → Integrations → App Store Connect API. Download the
.p8file and note the Key ID + Issuer ID. Export as env vars for Fastlane:export APP_STORE_CONNECT_API_KEY_KEY_ID="..." export APP_STORE_CONNECT_API_KEY_ISSUER_ID="..." export APP_STORE_CONNECT_API_KEY_KEY_FILEPATH="~/.appstoreconnect/AuthKey_XXXX.p8"
3. Signing + provisioning
- Set
DEVELOPMENT_TEAMin Xcode. OpenCairn.xcodeproj→ Signing & Capabilities → check “Automatically manage signing” + select your team. Persists acrossmake generateruns. - (optional) Fastlane Match setup —
make setup-certs. Only needed for multi-machine / CI / multi-developer setups where shared cert distribution matters. For a single dev on a single Mac with Xcode’s automatic signing, skip this entirely;make betawill use whatever signing Xcode is configured for. Match adds a private encrypted git repo to manage certs across machines, which is overkill for solo work.
4. Build + smoke test
- Re-capture screenshots.
make screenshots. UI has shifted since the last capture (sync card v2, Settings color icons, scroll-to-top, eager-persist-defers UI). Required before App Store submission; not required for TestFlight beta. - First TestFlight build.
make beta— builds IPA, uploads to TestFlight. No screenshots / metadata required for TestFlight; only the IPA + ASC API key. - Smoke test on a real device via TestFlight. Validate: PhotoKit enumeration against a real library, end-to-end sync against your Immich, background refresh scheduling (simulator lies about
BGAppRefreshTask).
5. Reviewer access
- Reviewer Immich instance.
docs/app-store-review-notes.mdhasREPLACE-BEFORE-SUBMISSIONplaceholders for URL + API key. Either (a) spin up a dedicated Immich kept live during Apple’s review window (~3–7 days), or (b) point reviewers at demo.immich.app with a note about rate limits. Apple has historically accepted option (b).
6. Submit
make release— builds IPA, uploads to App Store Connect.- Paste metadata from
docs/app-store-metadata.md(description, keywords, subtitle, support URL, marketing URL). - Set category + age rating. Photo & Video primary, Utilities secondary, 4+. Answers pre-drafted in
docs/app-store-metadata.md. - Paste privacy labels from
docs/app-store-privacy-labels.md. - Paste review notes from
docs/app-store-review-notes.md(with real reviewer credentials filled in). - Privacy policy URL.
https://glarue.github.io/cairn/PRIVACY - Upload screenshots from
iOS/fastlane/screenshots/en-US/. - Record and attach screen recording. Full end-to-end flow (setup → index → delete photos → sync → confirm trash → restore). Attach as App Review Attachment.
- Submit for review.
Already done
- Fira Code license file.
LICENSES/FiraCode.OFL.txt— OFL-1.1 text with correct copyright line. - Accessibility pass. VoiceOver labels on all icon-only buttons,
.accessibilityValue()on custom controls,accessibilityReduceMotiongating on all animations. - Version & build numbers.
CFBundleShortVersionString: "0.1.0"+CFBundleVersion: "1"iniOS/project.yml. - App icon. Light + dark variants in place.
- Screenshot pipeline.
make screenshotsproduces light + dark sets for two device sizes. - Demand-evidence doc.
docs/IMMICH_DEMAND.md— direction-verified list of GitHub discussions to cite + post on. Use #4341 as the primary citation. - Privacy policy. Live at
https://glarue.github.io/cairn/PRIVACY.
Post-launch / nice-to-haves
Not blockers. Move up the priority list only if a user flags them.
- Snapshot tests for SwiftUI screens.
swift-snapshot-testingfrom Point-Free. Priority targets: Setup steps, DryRunSheet phases, PendingReviewScreen variants. - Background task validation on real device. Simulator lies about
BGAppRefreshTaskscheduling. One overnight charging run on hardware would confirm the real behavior. - Local OS notifications for backlog alerts. In-app banner exists; next step is
UNUserNotificationCenterpermission + edge-triggered fire fromhandleBackgroundRefresh+ deep link. cairn/v2tag schema. Currently on v1. No pressure to bump; noting the extensibility hook for when a breaking change to run-breadcrumb shape is needed.- Android port. Deliberately deferred.
CairnCorestays pure Foundation + CryptoKit so a Kotlin port is tractable. Decision criteria and port order live in the plan doc’s “Portability” section. - Push full source to GitHub. Force-push
mainover the skeleton branch once the app is in review.
Last updated: 2026-04-23. Keep this file honest — either mark items done or remove them when stale.