Skip to content
C.W.K.
Stream
Lesson 06 of 06 · published

From an Empty Folder to a Build on the Phone

~18 min · proof-and-fleet, synthesis, capstone, testflight, workflow

Level 0Bundle Opener
0 XP0/81 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete
"Not because Apple is pretty. That much a year is worth it."

One Path Through Everything

This quest has followed one small family of apps, Spark, from a package to a phone. This lesson puts the whole path in one place, in the order the pieces have to exist, with the lesson behind each step. The order is the point: several of the traps in earlier tracks were a step done in the intuitive order instead of the real one.

The Shared Logic First

Start with a Swift package in Swift 6 language mode, because a data race should be a compile error on your desk rather than a trap on someone's phone. Put the values, the Codable types and the rules both apps share in a library target. Give every stored document an explicit init(from:) so the first added field migrates instead of wiping a file. Test the pure logic with Swift Testing where it lives, and remember that a closure handed to an Objective-C API from a main-actor context is a review item, not a detail.

The Mac App

Pick the shape: AppKit owns windows and text where input composition and split views matter, SwiftUI hosts settings and forms, and @Observable models feed both. Build with SwiftPM and a script that assembles the bundle, strips extended attributes, signs with the one self-signed identity, installs by rename and verifies at the final path. Check that the designated requirement names the certificate, not a hash, so permissions survive the next build. If it is a utility, give it a LaunchAgent that restarts it only after a failure. Prove the installed bundle with a hash check and bounded stages, and ship copies of that one build to the other Macs.

The Phone App

Generate the Xcode project from project.yml with one universal target, the team written in source, a share extension and an App Group, and a watch app if the capture belongs on the wrist. Keep it a thin client: a strict engine location, the HTTPS twin or the lone ATS key, a byte budget on every call, a socket that reconnects without replaying commands, and an outbox where the local write is the capture. Run the tests on a dedicated simulator and make every skip loud, then remember what the Simulator cannot exercise: a microphone, Data Protection, a real share from Mail.

Delivery

Archive first, so the identifiers exist; then create the App Store Connect record by choosing the bundle id. Authenticate with a team API key. Let the pipeline refuse dirty inputs, bump the build number in the source, archive with a kept log, gate the archive, and upload behind a status-and-banner check. What it prints is fact one of four. Processed, assigned and installed are separate facts, and installed is a number read on the device.

None of this works on a free account, which is the condition this quest set at the start. The membership pays for TestFlight, App Store Connect records, App Groups across extensions and push keys, and every later track stands on those. The rest is the discipline this last track is named for: every claim in the path above was measured by someone before it became a rule, and the next claim you add should be too.

Code

The repository at the end of the quest, with the track behind each piece·text
spark/
├── Package.swift                    swift-tools 6.x, Swift 6 language mode        (track 4)
├── Sources/SparkCore/               values, Codable with explicit init(from:)     (track 2)
├── Sources/Spark/                   the Mac app: AppKit shell, SwiftUI settings   (track 5)
├── Tests/SparkCoreTests/            Swift Testing for pure logic                  (track 4)
├── scripts/build-app.sh             assemble, sign (self-signed), install, verify (track 6)
├── scripts/test-installed-app.sh    hash identity, bounded stages                 (tracks 7, 13)
├── deploy/com.example.spark.plist   Aqua LaunchAgent, KeepAlive on failure only   (track 7)
├── clients/apple/SparkMobile/
│   ├── project.yml                  ONE universal target, team in source          (tracks 4, 8)
│   ├── Sources/ ShareExtension/ Watch/                                           (tracks 8-10)
│   └── Resources/Info.plist         ATS key alone, export compliance answered     (tracks 11-12)
└── scripts/mobile-testflight.sh     + mobile-testflight.config.zsh + upload gate  (track 12)
The whole run, in order, and where the words stop·bash
# 1. The package: the logic both apps share, proven where it lives.
swift build && swift test

# 2. The Mac app: built, signed with the stable identity, installed, proven installed.
scripts/build-app.sh
codesign -d -r- /Applications/Spark.app 2>&1 | grep 'certificate leaf'   # not a cdhash
scripts/test-installed-app.sh

# 3. The phone app: generate, test on a DEDICATED simulator, and make skips loud.
(cd clients/apple/SparkMobile && xcodegen generate)
SPARK_SIMULATOR_ID=<udid> scripts/test-mobile.sh

# 4. Delivery: plan, then upload through every guard.
scripts/mobile-testflight.sh --plan
scripts/mobile-testflight.sh --upload     # fact 1 of 4

# 5. Then, and only then, the words: processed, assigned, installed (read on the device).

External links

Exercise

Start from an empty folder and take Spark the whole way: package, Mac app installed and proven, phone app with a share extension, an engine it talks to over your private network, and one TestFlight build. Keep a log with one line per step: the command, what it printed, and which lesson's rule it satisfied. End with a four-fact delivery report for the build, and a list of every step where you had to measure something the lessons did not tell you.
Hint
The last list is the useful one. Anything on it that surprised you is a candidate for your own house rule, written with its reason and its cost, the way this quest's house rules were.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.