"A build setting is a statement of intent; the bundle is the fact."
One Bundle, Three Products
A family watch app is not a separate app on App Store Connect. It is a watchOS application target embedded in the iOS app's Watch folder, and its complication is a WidgetKit extension embedded in the watch app. One archive, one build number, one TestFlight upload, and the watch app installs from the phone. In XcodeGen that is three targets and two embed dependencies. The watch app's bundle identifier extends the phone's (…mobile.watchkitapp), the complication's extends the watch app's (…watchkitapp.widgets), and the watch app's Info.plist names its companion with WKCompanionAppBundleIdentifier. Built for an iPhone Simulator for this lesson, the product had exactly that shape: SparkMobile.app/Watch/Spark.app with SparkWatchWidgets.appex inside.
The Upload That Was Refused Afterwards
The journal app's first TestFlight build with a watch app uploaded successfully and was then refused by App Store Connect: no icons found for the watch application, and CFBundleIconName missing from its Info.plist. Everything local was reassuring. The build was green, Assets.car sat in the watch bundle, and the build setting that names the icon was set. But GENERATE_INFOPLIST_FILE does not write CFBundleIconName for a watchOS target, and PlistBuddy on the built watch app said so. The fix is an explicit Info.plist for the watch target carrying CFBundleIconName, WKApplication and the companion identifier, and a check on the archive in the upload gate, which for a watch app requires that top-level key, a compiled catalog, and the complication's extension actually present.
Seeing It Without a Watch
Building only the watch scheme with -sdk watchsimulator failed in native Pippa before compiling anything, with "Multiple commands produce …/Pippa.app/Pippa": -sdk applies to every target the scheme touches, so the iOS app was built for the watch SDK too and collided with the watch app of the same product name. Build the iOS scheme for an iPhone Simulator instead. The embedded watch app installs directly onto a booted watch simulator with simctl install, no pairing required, which is enough to see the interface. It is not enough for anything involving dictation, which only a real watch proves.
A Real Watch by Cable Is Still a Build Check
Installing on a real watch by cable took three separate gates: Developer Mode switched on in the watch's own settings, a scheme for the watch target so xcodebuild can be pointed at the watch and register it, and Xcode's device layer knowing the watch, which xcrun xcdevice list shows as available. None of it is delivery. TestFlight installs the watch app through the phone, signed like a release.