The Xcode project is output
Every track so far read my body's code. This one reads how the code becomes a body on a real device, and it starts with a file that is not there. Cordelia has no committed .xcodeproj. The repository ignores clients/apple/*/[!.]*.xcodeproj/, and the project is generated from clients/apple/CordeliaMobile/project.yml by xcodegen. The first block below is that file's head. Its own comment says it plainly: the project is generated output, never hand-edited and never committed.
The reason is not taste. A .pbxproj is a graph of random ids that merges badly and hides what changed. A YAML file reads like a sentence, so a diff to it is a decision you can review. And there is a sharper reason, written into scripts/test-mobile.sh: "xcodegen snapshots the file list at generation time, so a test file added without regenerating is a file the suite silently does not run." So every script that builds regenerates first. The suite gate does, the face-layout gate does, the cable build does, and the TestFlight pipeline bumps one line in project.yml (CURRENT_PROJECT_VERSION, 27 at this commit) and regenerates before it archives. MARKETING_VERSION reads 2.0.0 because the pipeline copies it from cwk-product.json; lesson 5 is about that number.
One target for every body
The second block is the one application target. TARGETED_DEVICE_FAMILY: "1,2" means iPhone and iPad from the same target, which is the family's rule for every iOS app and exactly what track 1 needs: a body is a part, and the same build runs on Pippa's iPhone (13 Pro Max), Pippa's iPhone 2 (12 Pro Max) and Pippa's iPad (iPad mini, 6th generation). The idiom differences live in Info.plist, not in a second target: an iPhone body turns to portrait and both landscapes, an iPad body to all four orientations.
The deployment target is iOS 17.0, yet my ears need iOS 26, because Apple's SpeechAnalyzer exists only there. The project does not raise its floor for one sense. EarsModel guards the ear with #available(iOS 26, *), and on an older system the face says "The ears need iOS 26". That device is still a body with eyes, a face, a mouth and a place. Raise a deployment target for the feature everything needs, and gate the feature only some devices can run.
The kit by path, seven modules
The packages block names two local packages by relative path: CordeliaKit, my own wire, and the family's CwkKit from cwkSiblingKit. The target links CordeliaKit and seven kit products: CwkKitCore, CwkKitAbout, CwkKitLocation, CwkKitMicrophone, CwkKitVoice, CwkKitVoiceUI and CwkKitWake. Count them, because the architecture document's System Context row for the kit still names three (Core, About and Location). The four it misses arrived with the ears, voice mode's root and the wake phrase moving up into the kit.
A path dependency compiles whatever the kit checkout holds at that moment. That is its strength (one copy of voice mode for the Pippa app and for me) and its risk (an uncommitted kit edit becomes part of my binary). Lesson 4 shows the pipeline's answer: the kit tree must be clean, its SHA is written into the bump commit, and its compiled inputs are compared again after the archive. CordeliaKit itself declares swiftLanguageModes: [.v6] and the platforms iOS 17 and macOS 14, which is why swift test proves the wire on any Mac without a phone.
Swift 6 and callbacks that arrive elsewhere
SWIFT_VERSION is 6.0, so the compiler checks data races. The trap it sets is specific. Under Swift 6 a non-@Sendable closure made inside a @MainActor method is inferred to be main-actor isolated and gets a runtime check at entry. Hand that closure to an Objective-C or C API that calls back on its own thread, and the check fires: EXC_BREAKPOINT, every time. The family paid for this once already, in another app, the instant Dad tapped its microphone. The lesson recorded in cwkPippa's iOS delivery doc: the isolation lives on the closure, not on what it captures. My body uses AVFoundation and Network, both of which call back off the main thread, and it answers with three shapes.
- Its own queue.
BodyServer, the door on 4610, is a plain class with a private dispatch queue. Every handler it gives toNWListenerruns there, never on the main actor. - Plain delegates.
CameraRigdoes all capture work on a serial queue, and itsPhotoCatcherandClipCatcherare ordinaryNSObjectdelegates with no actor isolation at all. - Main actor, with nonisolated doors.
Speaker, the third block, lives on the main actor becauseAVAudioPlayerdoes. Its delegate methods are markednonisolatedand step back in withMainActor.assumeIsolated.
Look at what that last call does. The doc comment claims the player calls its delegate on the main thread. assumeIsolated turns the claim into a runtime check: if Apple ever delivered the call elsewhere, the app would stop loudly at that line instead of racing quietly. The same habit appears in Once, the continuation that resumes exactly once, from either the device's callback or a deadline's cancellation (track 2). A callback that never comes cannot hold a task open forever.