Files
wristcode/watch
gibandClaude Opus 5.5 f772f4e4c4 Watch: TestFlight archive, iOS stub container, testflight.sh
The App Store only accepts a watch-only app inside an empty iOS stub
(watchapp2-container), the pair Xcode's Watch-only App template makes.
The stub takes org.gbrown.wristcode; the watch app, and so the APNs topic,
becomes org.gbrown.wristcode.watchkitapp.

Adds ITSAppUsesNonExemptEncryption = NO and scripts/testflight.sh, which
archives with API key automatic signing and a timestamp build number,
then exports and uploads (or exports only with --no-upload).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-01 10:08:55 -05:00
..

WristCode watch app

SwiftUI, watch only, Swift 6 with strict concurrency, and no dependencies. It talks to the gateway over HTTPS + JSON as described in docs/api.md; WristCode/API/Models.swift mirrors gateway/src/contract.ts.

Build and test

cd watch
xcodegen generate            # the .xcodeproj is generated and gitignored
xcodebuild -scheme WristCode -destination 'platform=watchOS Simulator,name=Apple Watch Series 12 (46mm)' build
xcodebuild -scheme WristCode -destination 'platform=watchOS Simulator,name=Apple Watch Series 12 (46mm)' test

TestFlight

The App Store only takes a watch-only app inside an empty iOS stub, so project.yml has two app targets, the same pair Xcode's "Watch-only App" template makes:

Target Bundle ID Job
WristCodeContainer org.gbrown.wristcode iOS stub that owns the App Store Connect record and embeds the watch app
WristCode org.gbrown.wristcode.watchkitapp The watch app, and the APNs topic
scripts/testflight.sh              # archive, export and upload to App Store Connect
scripts/testflight.sh --no-upload  # archive and export an .ipa to build/export-<n> only

The build number defaults to a YYYYMMDDHHMM timestamp (override it with BUILD_NUMBER). Signing is automatic through the App Store Connect API key in ASC_KEY_PATH, ASC_KEY_ID and ASC_ISSUER_ID, which default to the key in ~/.appstoreconnect/private_keys. The entitlements file says aps-environment: development; the App Store export re-signs it as production. Release builds register their token with the gateway as production, Debug builds as sandbox.

Demo mode

DemoGateway serves fixture data shaped like the docs/api.md examples, and it reacts to writes: Allow moves a thread to working, a reply restarts a finished thread. The SwiftUI previews use it, the pairing screen has a "Try the demo" button, and launch arguments open a screen directly:

xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo                                # feed
xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo -thread littleman/t-next       # thread
xcrun simctl launch <udid> org.gbrown.wristcode.watchkitapp -demo -screen new                    # new thread

screenshots/ holds the screens captured this way.

Layout

Path Job
WristCodeApp.swift App entry, root navigation, WKApplicationDelegate for APNs registration
AppModel.swift Paired, demo or unpaired; navigation path; the feed; 401 sends you back to pairing
API/GatewayClient.swift GatewayAPI protocol and the URLSession client
API/Models.swift Codable mirror of the contract
Auth/DeviceStore.swift Device token and gateway URL in the Keychain
Demo/DemoGateway.swift In-memory gateway with fixtures
Views/ Pair, Feed, Thread, New thread, and the mock B theme
Notifications/NotificationHandler.swift Categories approval, finished, failed, input, host and their actions