# Thingtime iOS Agent Runbook

This directory contains the native iOS shell for Thingtime. Treat
`project.yml` as the source of truth; generated Xcode project files are build
artifacts and should stay untracked.

## Apple Developer And TestFlight Flow

- Use `scripts/testflight-beta.sh` from this `iOS/` directory for TestFlight
  uploads. It loads ignored values from `iOS/.env` when present and then runs
  `bundle exec fastlane beta`.
- Keep all Apple account values out of git. Put real values only in the shell
  environment or ignored `iOS/.env`; keep `.p8` files in the local/private
  config folder, not in the repo.
- Required upload env:
  - `ASC_KEY_ID`: App Store Connect API key ID for the individual Apple account
    that can see the Thingtime app.
  - `ASC_ISSUER_ID`: issuer ID from App Store Connect Users and Access, or
    blank for an individual App Store Connect API key.
  - `ASC_KEY_CONTENT`: base64-encoded `.p8` contents. The current Fastlane lane
    expects key content, not a key filepath.
  - `DEVELOPMENT_TEAM`: Apple developer team/App ID prefix used for Thingtime.
  - `PRODUCT_BUNDLE_IDENTIFIER`: `com.thingtime.appletime`.
  - `THINGTIME_WEB_URL`: optional webview URL; omit it to build
    `https://thingtime.com`, or set it to a Vercel branch deployment for
    TestFlight preview builds.
- Before spending time on signing problems, verify App Store Connect auth. The
  key, issuer when applicable, and `.p8` must be able to call the App Store
  Connect API and list the Thingtime app by bundle ID. A 401 from Apple means
  the key ID, issuer ID, or `.p8` file do not match, even if the filename looks
  right.
- Do not reuse LiDAR-project keys, issuer IDs, bundle IDs, or provisioning
  assumptions. Thingtime's app record is the source of truth for this app.
- If several local `.p8` files exist, test each candidate key against App Store
  Connect and use the one that can see the Thingtime app. Do not infer the
  correct key from filename alone.
- Use an Xcode install that has the required iPhoneOS SDK. For beta SDKs, set
  `DEVELOPER_DIR` explicitly, for example:

  ```sh
  export DEVELOPER_DIR="/Applications/Xcode-beta.app/Contents/Developer"
  xcodebuild -runFirstLaunch
  xcrun --sdk iphoneos --show-sdk-version
  ```

- App Store Connect may reject uploads built with an unsupported beta SDK or
  non-RC beta Xcode as `90534 Unsupported SDK or Xcode version`. Rebuild and
  upload with a supported release or RC Xcode by setting `DEVELOPER_DIR` for
  the upload command, for example
  `/Applications/Xcode.app/Contents/Developer`.

- The Fastlane lane syncs an Apple Distribution certificate and App Store
  provisioning profile through the App Store Connect API before archiving. Use
  `SKIP_CERT_SYNC=1` or `SKIP_PROFILE_SYNC=1` only when the correct signing
  asset is already installed and you intentionally want to skip that sync.
- If export fails with a cloud signing permission/profile lookup error while a
  valid App Store profile is already installed, set
  `PROVISIONING_PROFILE_SPECIFIER` to that installed profile name. The lane
  will use manual export mapping only when this variable is present.
- For each TestFlight upload, bump `CURRENT_PROJECT_VERSION` in `project.yml`.
  `CFBundleVersion` and `CFBundleShortVersionString` are generated from
  `project.yml`; do not edit generated `.xcodeproj` files to change versioning.
- After a successful upload, verify the IPA metadata when useful:

  ```sh
  /usr/bin/unzip -p build/Thingtime.ipa 'Payload/Thingtime.app/Info.plist' | \
    plutil -p -
  ```

## Liquid Glass App Icon (`AppIcon.icon`)

- The iOS 26+ home-screen icon is the Icon Composer package at
  `Thingtime/Resources/AppIcon.icon` (JSON manifest + SVG layers). The legacy
  `Assets.xcassets/AppIcon.appiconset` stays as the pre-iOS-26 fallback; both
  share the name `AppIcon` on purpose.
- Manifest gotchas (actool crashes with a nil-array exception instead of a
  real error): `color-space-for-untagged-svg-colors` only accepts
  `display-p3`, and `supported-platforms.squares` must be an array like
  `["iOS"]`. Keep brand colors exact by using per-layer
  `fill-specializations` with `srgb:` solid values rather than untagged SVG
  colors.
- On a beta-macOS host, release Xcode 26.5's actool crashes on EVERY `.icon`
  compile while Xcode-beta 27's actool works. Workaround used for build 12
  (only needed while that mismatch persists): move `AppIcon.icon` aside, run
  `bundle exec fastlane build` on release Xcode, compile the catalog
  standalone with the beta actool (`actool AppIcon.icon Assets.xcassets
  --compile out --app-icon AppIcon --output-partial-info-plist out/pi.plist
  --compress-pngs --target-device iphone --target-device ipad
  --minimum-deployment-target 17.0 --platform iphoneos`), swap
  `Assets.car` + the two fallback `AppIcon*.png` into the IPA's app bundle,
  re-sign with the Apple Distribution identity plus the app's own
  entitlements, then upload with `bundle exec fastlane upload`
  (`IPA_PATH=<swapped.ipa>`). Verify the car with
  `xcrun assetutil --info` (expect an `Icon Image` record named `AppIcon`).
- Do not upload archives built with a non-RC beta Xcode; App Store Connect
  rejects them as `90534`.

## Common Command

```sh
cd iOS
cp .env.example .env
# Fill .env with the required Apple values, then encode the matching p8 key:
export ASC_KEY_CONTENT="$(base64 -i /path/to/AuthKey_KEYID.p8)"
scripts/testflight-beta.sh
```

## Simulator Testing

- `scripts/test.sh` defaults through the shared build destination, which may be
  a generic simulator destination. If Xcode rejects that for tests with "Tests
  must be run on a concrete device", pick an available simulator with
  `xcrun simctl list devices available` and rerun, for example:

  ```sh
  DEST='platform=iOS Simulator,id=<simulator-uuid>' ./scripts/test.sh
  ```
