How Frank's Fed builds get to testers, how tester comments get back to us, and how UAT closes the loop. Both directions are now wired up — this page is the operating manual.
Two Claude Code skills, one for each direction. Both share the same App Store Connect API key and the same git repo state, so there's only one copy of credentials, one source of truth for build numbers, and one place to look when something goes wrong.
| Direction | Skill | What it does |
|---|---|---|
| Out | /testflight |
Bumps the build number, asks Claude Haiku to write voice-aware release notes from the recent git commits, archives + uploads to App Store Connect, then posts the "What's New" text and verifies it read-back. Tags the release. |
| In | /franks-fed-pull-feedback |
Pulls TestFlight tester comments + screenshots + crash logs from App Store Connect into .planning/inbox/testflight-feedback.md as triage items. Idempotent — re-runs skip already-seen submissions. |
testflight/v{VERSION}-build-{N} tag annotation, so the repo is self-documenting.The Claude Haiku prompt enforces Frank's Fed voice (Bible §13). If a deploy ever posts notes that contain a banned word, that's a regression worth tightening — the notes go through ASC API rejection of emoji, but not banned-word checking. Open improvement; the hand-written fallback notes (used when Haiku returns empty) are pre-vetted.
Every pulled submission ID lives in .planning/inbox/.testflight-feedback-state.json alongside last_pulled_at. Re-running /franks-fed-pull-feedback picks up where you left off. The state file is committed — so dedupe survives across dev machines.
Whether a tester comment belongs to Phase 5 (Memorial dignity), Phase 4 (helper invite friction), or Phase 6 (a brand-new bug) is a judgement call that needs context the API doesn't give us. The inbox is a holding queue. A future /gsd-triage-feedback skill could batch-classify with Haiku, but for the closed-beta cohort the manual triage is the right shape — it's also the moment the founder reads each comment in full, which we don't want to skip.
Phase 5 (Memorial UI + Safe-to-Walk + Voice/A11y polish + Notifications) needs your real-device verification before it can mark verified. Here's the sequence:
/testflight. The skill takes ~5–10 minutes; Apple takes ~10–30 more to process. You'll get an email when it's ready in TestFlight.
.planning/phases/05-memories-safe-to-walk-voice-a11y-notifications/05-A11Y-AUDIT.md.
.planning/phases/05-memories-safe-to-walk-voice-a11y-notifications/05-HUMAN-UAT.md. If you find any dignity-violating copy or bad VoiceOver labels, list them as gaps with the exact surface + suggested fix.
approved — both the a11y audit and walkthrough are clean (or you're shipping the fixes inline before signing off). Plan 09 Task 3 fires, the verifier runs, Phase 5 closes.blocking issues: <list> — describe each gap. Claude opens fix tasks before Phase 6 unblocks.skip walkthrough — not recommended. The walkthrough is the only end-to-end dignity test before real pet owners use the product.Run /franks-fed-pull-feedback after a few testers have had the build for a day or two. New comments + screenshots land in .planning/inbox/testflight-feedback.md with a triage checklist. For each one, decide:
HUMAN-UAT.md as a new human_verification entry, keeping the submission ID in the header (e.g. submission_id: abc12345). Or file it on BACKLOG.md. Or mark won't-fix.The /gsd-close-uat skill keys on submission IDs to mark items resolved when their fix commits land. The UAT board on this site updates automatically on the next publish.
| File / path | Purpose |
|---|---|
scripts/deploy-testflight.sh | Outbound deploy + Haiku notes + post-and-verify "What's New" |
scripts/submit-whats-new.sh | JWT signing + ASC API polling + betaBuildLocalization create/PATCH/verify |
scripts/pull-tester-feedback.sh | Inbound pull of betaFeedbackScreenshotSubmissions + betaFeedbackCrashSubmissions |
scripts/ExportOptions.plist | method=app-store-connect, destination=upload, Team DF3ZPT9XFN |
.claude/skills/testflight/SKILL.md | Project-scoped /testflight skill (overrides InfoVoice's user-level skill in this repo) |
.claude/skills/franks-fed-pull-feedback/SKILL.md | /franks-fed-pull-feedback skill |
private_keys/AuthKey_*.p8 | App Store Connect API key — gitignored. Falls back to ~/private_keys/ if the project dir doesn't have one. |
build/whats-new.md | Generated release notes, latest deploy |
build/archive.log | Full xcodebuild output — first place to look on archive failure |
.planning/inbox/testflight-feedback.md | Inbound tester comments + screenshots + crashes (append-only, deduped) |
.planning/inbox/.testflight-feedback-state.json | Dedup state — seen_ids[] + last_pulled_at |
Docs/testflight-deployment.md | In-repo operational reference (longer than this page) |
None of this works until the Apple side is registered. Set these up in Apple Developer Portal + App Store Connect, in this order:
be.780.franks-fed registered (capabilities: Push Notifications, Sign in with Apple, App Groups).nse, .widget, .tests, .uitestsgroup.be.780.franks-fed registered + linked to the three runtime app IDsbe.780.franks-fedThe skills will fail loudly with an actionable error message if any of these is missing on first run. scripts/submit-whats-new.sh's Could not look up App ID for be.780.franks-fed is the most common one — that's the App Store Connect record not being created yet.
| Field | Value |
|---|---|
| Issuer ID | ffbb8a56-2513-4d2e-b6cc-d6dbbe94762f |
| Primary Key ID | JR5N75FS49 |
| Fallback Key ID | 9F5BTGY6J2 |
| Bundle ID | be.780.franks-fed |
| Team ID | DF3ZPT9XFN |
Same Apple developer account, same Issuer, same .p8 keys as InfoVoice. The script looks in the project's private_keys/ first, then falls back to ~/private_keys/ — so cloning the repo on a new machine doesn't strictly require copying keys into the project, as long as you have them in your home dir.