Ship MuonPlayer to TestFlight #6

Merged
pecheny merged 4 commits from feat/testflight-release into main 2026-07-27 22:43:04 +00:00
Owner

Adds scripts/testflight.sh, which archives, exports, validates and uploads
either platform.

Drops the Last.fm password from Secrets.swift, adds a privacy manifest and a
privacy policy, and seeds an empty library with a bundled demo album.

Why

None of this could be distributed. The binary carried the password as a readable
literal, an upload using a required-reason API without a manifest is rejected,
and guideline 5.1.1(i) wants a policy linked in the app. A fresh install opened
on nothing, which is all an App Review device would ever see.

Archiving also showed the iOS target was built for iPad, and that a Release
archive needs the Mac target pinned to arm64.

Adds `scripts/testflight.sh`, which archives, exports, validates and uploads either platform. Drops the Last.fm password from `Secrets.swift`, adds a privacy manifest and a privacy policy, and seeds an empty library with a bundled demo album. # Why None of this could be distributed. The binary carried the password as a readable literal, an upload using a required-reason API without a manifest is rejected, and guideline 5.1.1(i) wants a policy linked in the app. A fresh install opened on nothing, which is all an App Review device would ever see. Archiving also showed the iOS target was built for iPad, and that a Release archive needs the Mac target pinned to arm64.
A string literal in Secrets.swift is readable in the App Store build with
strings(1). The credentials were only ever used by the MUON_LOGIN_TEST
harness, which can take them from the environment instead; the app itself
has had a login form since it had scrobbling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The App Store rejects an upload that uses a required-reason API without a
manifest; ours are UserDefaults and the mtime reads the scanner does. The
manifest ships in both bundles because it sits in the shared source dir.

It declares no collected data. A scrobble goes to the user's own Last.fm
account under their own credentials, so neither we nor a partner of ours
can reach it, and with no login the app never contacts Last.fm at all —
Apple's test is who gains access, not whether bytes leave the device.

Guideline 5.1.1(i) wants the policy linked inside the app as well as in
App Store Connect, so both About screens now link it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A fresh install opens on nothing, which is also all an App Review device
would ever see — the iOS library is whatever the user has put in
Documents. Two tracks are now copied in the first time the app starts
with nothing to play, once ever, so a demo somebody deletes stays gone.

The audio is synthesised by scripts/gen-demo-audio.py rather than sourced,
so nothing in the bundle carries a third-party licence. The two pieces are
one performance split mid-phrase: the join is where gapless playback can
actually be heard. They live under App/, which the Mac target excludes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both Info.plists now read $(MARKETING_VERSION)/$(CURRENT_PROJECT_VERSION),
so the version lives in project.yml alone. The build number defaults to the
commit count: App Store Connect refuses a re-used one, and a commit count
only ever goes up.

No Apple ID is signed into Xcode, so automatic signing has to authenticate
with the same App Store Connect API key the upload uses, or it cannot mint
the distribution certificate.

Two settings the archive forced out into the open. The iOS target shipped
as TARGETED_DEVICE_FAMILY 1,2 and so was reviewed as an iPad app, with an
iPad-shaped icon; the UI is iPhone-only. And a Release archive builds every
architecture rather than the active one, so the Mac target needs pinning to
arm64 until Vendor/FFmpeg carries an x86_64 slice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pecheny deleted branch feat/testflight-release 2026-07-27 22:43:04 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pecheny/muon-player!6
No description provided.