Files
wristcode/watch/README.md
T
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

63 lines
3.0 KiB
Markdown

# 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](../docs/api.md); `WristCode/API/Models.swift`
mirrors [gateway/src/contract.ts](../gateway/src/contract.ts).
## Build and test
```sh
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 |
```sh
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:
```sh
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 |