Skip to main content

continuum-release

Use when building, running, screenshotting, or releasing the Continuum iOS app — launching it in the simulator, driving its UI to verify a visual change, or shipping a version to TestFlight/App Store. Contains the verified account facts, the toolchain floor (iOS 26 SDK), the signing setup, and the archive→upload→submit runbook.

Quellinformationen

Repository
ryanfrigo/continuum
Letzte Quellaktivität
30. September 2026 um 17:26
Erkannte Sprache von SKILL.md
Englisch
Sterne
1
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
continuum-release
description
Use when building, running, screenshotting, or releasing the Continuum iOS app — launching it in the simulator, driving its UI to verify a visual change, or shipping a version to TestFlight/App Store. Contains the verified account facts, the toolchain floor (iOS 26 SDK), the signing setup, and the archive→upload→submit runbook.
# Continuum release & verification runbook SwiftUI + SwiftData habit tracker with a widget extension. Bundle `orion-labs.continuum`, widget `continuumWidget`, app group `group.com.orionlabs.continuum`, CloudKit container `iCloud.com.orionlabs.continuum`. Release *gates* (what to test before shipping) live in `RELEASE_CHECKLIST.md`. This file is the *mechanics*: commands, toolchain floors, signing, and how to verify a change visually. ## Verified account facts | Thing | Value | |---|---| | Team ID | `NVN2NY8GZC` | | ASC API key ID | `6M245PSNS9` | | ASC key path | `~/.appstoreconnect/private_keys/AuthKey_6M245PSNS9.p8` | | ASC issuer UUID | `9603f3d8-7877-4300-93fb-f740c62cd44e` (also a GitHub secret). One key = one team; this orion-labs key covers Continuum, WakeEarly and Siftbird. | | Apple ID | `rf@stoaked.co` | ## Toolchain floor — check FIRST, it invalidates everything else Since **April 28, 2026** App Store Connect rejects any iOS upload not built with the **iOS 26 SDK**, i.e. **Xcode 26+**. Verify before any release work: ```bash xcodebuild -version # need 26.x xcodebuild -showsdks | grep -i "iOS " # need iphoneos26.x sw_vers -productVersion # Xcode 26.0–26.3 need macOS >= 15.6 ``` - Xcode **26.0–26.3** run on macOS Sequoia **15.6+**. - Xcode **26.4+** requires macOS Tahoe **26.2+**. - Pick the newest Xcode matching the installed macOS; don't upgrade macOS just to get a newer Xcode unless something needs it. Install a specific version without the Mac App Store: ```bash brew install xcodes xcodes list | grep -E "^26\.[0-9.]+ \(" | grep -viE "beta|candidate" xcodes install 26.3 # prompts for Apple ID + 2FA — needs a human xcodes select 26.3 ``` `xcodes install` needs interactive Apple ID auth. An agent cannot complete the 2FA prompt alone; hand this step to the user. ### You probably don't need local Xcode 26 at all — build on CI **Preferred path.** `.github/workflows/release.yml` builds on GitHub's `macos-26` runner, which ships **Xcode 26.6** (and 26.0–26.3). That sidesteps the local toolchain, the macOS upgrade, and the ~10 GB download completely: ```bash gh workflow run release.yml # build what's on main gh workflow run release.yml -f build_number=5 # ASC rejects duplicate build numbers gh run watch $(gh run list -w release.yml -L1 --json databaseId -q '.[0].databaseId') ``` All three repo secrets are set (`gh secret list` to confirm): `ASC_KEY_ID`, `ASC_KEY_P8_BASE64`, `ASC_ISSUER_ID`. **This path shipped 3.4 to the App Store on 2026-08-11**, so it is proven, not theoretical. Check the live version before assuming what's shipped: ```bash curl -s "https://itunes.apple.com/lookup?bundleId=orion-labs.continuum" | python3 -m json.tool | grep -E '"version"|ReleaseDate' ``` Signing needs no p12: `-allowProvisioningUpdates` plus the ASC API key mints the Apple Distribution cert on the clean runner. ### Local Xcode 26 — only if you truly need to build on this Mac Every route ends at an Apple authentication boundary, so prefer CI above: | Route | Blocker | |---|---| | `xcodes install 26.x` | Apple ID + 2FA prompt | | Mac App Store / `mas` | signed-in App Store account | | developer.apple.com .xip | authenticated web session | | macOS update (needed to *run* Xcode 26) | `sudo` password, then a restart | Browser automation does not rescue the local path either. Both stacks were unavailable on 2026-08-11: `claude-in-chrome` reported "extension is not connected", and `chrome-devtools` MCP refused with "browser is already running for `~/.cache/chrome-devtools-mcp/chrome-profile`" (its Chrome uses `--remote-debugging-pipe`, so there's no TCP port to attach to, and `--isolated` isn't settable from the tool call). Even when it works, that MCP uses its own scratch profile with **no Apple session**, so a human sign-in is still required. **Conclusion: use CI.** The only thing a human must supply is the issuer UUID, because it lives solely behind an authenticated App Store Connect session. Record it in the table above so it's a one-time cost. Never attempt to ship with an older SDK — App Store Connect rejects it outright, so there is no partial-credit path, and never fake `DTSDKName` to look newer. ### Disk space Xcode 26 needs roughly **60–80 GB free** (≈10 GB .xip + expansion + install). Safe, fully regenerable things to clear, largest first: ```bash rm -rf "$HOME/Library/Developer/Xcode/iOS DeviceSupport" # tens of GB; rebuilt per device rm -rf "$HOME/Library/Developer/Xcode/DerivedData" # rebuilt on next build xcrun simctl delete unavailable # keeps available sims + their data brew cleanup --prune=all ``` Never clear `~/Documents`, `~/dev`, `~/models`, or `~/Library/Developer/Xcode/Archives` (real release artifacts). If a GateGuard hook blocks `rm -rf`, don't disable the hook — hand the user the command prefixed with `!` so they run it themselves. ## Signing The App Store needs an **Apple Distribution** identity. Check: ```bash security find-identity -v -p codesigning | grep -i distribution defaults read com.apple.dt.Xcode DVTDeveloperAccountManagerAppleIDLists # empty = no Xcode account ``` An `Apple Development` cert alone is **not** enough. Archiving still succeeds (development-signed, and `-allowProvisioningUpdates` quietly falls back to it), but export/upload fails — so check the archive's `SigningIdentity`, don't trust `ARCHIVE SUCCEEDED`. Fix by signing into Xcode (Settings → Accounts) as Team Agent/Admin, or let `-allowProvisioningUpdates` mint one once the ASC API key is supplied. ## Issuer ID **Recorded above.** It was in `.claude/skills/siftbird-appstore-ship` the whole time — same key, same team — while this file said it was unavailable and cost several sessions' worth of dead ends. If a value looks missing, grep the other skills for the key id before concluding it can't be had. Historical note on obtaining one: **App Store Connect → Users and Access → Integrations → App Store Connect API**, shown as "Issuer ID" at the top. It cannot be derived from the `.p8`. Once you have it, **write it into the table above** so future releases are unattended. Don't re-test these — all verified dead ends on 2026-08-11: - Key `6M245PSNS9` is **team-scoped**, not an individual key. The individual-key JWT form (omit `iss`, use `sub: "user"`) returns `401 NOT_AUTHORIZED`, so the issuer really is mandatory. - Not cached anywhere on this Mac: shell history, `~/.appstoreconnect`, `~/.fastlane`, `com.apple.dt.Xcode.plist`, the login keychain, `~/.config`. - No ASC endpoint returns it, and a wrong value just fails auth — never guess. - Browser rescue re-tested 2026-09-17: `claude-in-chrome` still reports "extension is not connected", and `chrome-devtools` MCP now runs but on a blank profile — appstoreconnect.apple.com bounces to `authResult=FAILED`. Don't retry; run it through CI instead. ## Release runbook **Use the script** — it encodes every preflight below and fails fast: ```bash ./scripts/release.sh <ASC_ISSUER_UUID> ``` It verifies the ASC key, the iOS 26+ SDK, the distribution identity, and version consistency across all 8 configs; runs the tests; archives; asserts the archive is *actually* distribution-signed; then uploads. It stops before submission on purpose. The manual equivalent, step by step: ```bash # 1. Preflight xcodebuild test -scheme continuum -testPlan continuum \ -destination 'platform=iOS Simulator,name=iPhone 16 Plus' # 93 tests as of 3.8 (incl. 2 screenshot seeds) # 2. Bump version — 8 occurrences each, app + widget + test targets. # Widget Info.plist already tracks $(MARKETING_VERSION)/$(CURRENT_PROJECT_VERSION). sed -i '' 's/MARKETING_VERSION = 3\.4;/MARKETING_VERSION = 3.5;/g; \ s/CURRENT_PROJECT_VERSION = 4;/CURRENT_PROJECT_VERSION = 5;/g' \ continuum.xcodeproj/project.pbxproj grep -o "MARKETING_VERSION = [0-9.]*;" continuum.xcodeproj/project.pbxproj | sort | uniq -c # 3. Archive xcodebuild archive -scheme continuum -configuration Release \ -destination 'generic/platform=iOS' \ -archivePath build/Continuum.xcarchive -allowProvisioningUpdates # verify what you actually built /usr/libexec/PlistBuddy -c 'Print :ApplicationProperties:CFBundleShortVersionString' \ -c 'Print :ApplicationProperties:CFBundleVersion' \ -c 'Print :ApplicationProperties:SigningIdentity' build/Continuum.xcarchive/Info.plist plutil -extract DTSDKName raw build/Continuum.xcarchive/Products/Applications/continuum.app/Info.plist # 4. Upload cat > build/exportOptions.plist <<'PLIST' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"><dict> <key>method</key><string>app-store-connect</string> <key>teamID</key><string>NVN2NY8GZC</string> <key>signingStyle</key><string>automatic</string> <key>destination</key><string>upload</string> </dict></plist> PLIST xcodebuild -exportArchive \ -archivePath build/Continuum.xcarchive \ -exportOptionsPlist build/exportOptions.plist \ -allowProvisioningUpdates \ -authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_6M245PSNS9.p8 \ -authenticationKeyID 6M245PSNS9 \ -authenticationKeyIssuerID <ISSUER-UUID> ``` ### Submitting for review — scriptable after all `scripts/asc-submit.mjs` does what the upload stops short of: create the version, set What's New from `scripts/whats-new.txt`, attach the build, turn on phased release, submit. ```bash node scripts/asc-submit.mjs --version 3.5 --build 6 # prepare; prints what it staged node scripts/asc-submit.mjs --version 3.5 --build 6 --submit # sends it to Apple ``` Prepare is idempotent — run it first and read the output. The build must report `VALID` (processing finished) or the attach leaves the version buildless and review bounces it. Used for 3.5 on 2026-09-21 → `WAITING_FOR_REVIEW`. Sign-off is a *people* gate, not a tooling one: run the script only once Ryan has said to ship. The gates in `RELEASE_CHECKLIST.md` are deliberate: upgrade-path test on real data, TestFlight soak, phased release ON (this app touches the data layer + CloudKit). ## CloudKit schema **A new `@Model` type does not sync until its record type exists in the PRODUCTION schema.** Nothing crashes when it's missing — the container falls back to local-only — so this fails completely silently. It bit hard: the schema was never deployed at all until 2026-09-21, meaning iCloud sync had never once worked for a real user since 3.3 shipped it as the headline feature in July. Check Production first, and don't trust the console UI alone: ```bash xcrun cktool save-token --type management # CloudKit console → Tokens & Keys xcrun cktool export-schema --team-id NVN2NY8GZC \ --container-id iCloud.com.orionlabs.continuum --environment production ``` To add a record type without a device or an iCloud sign-in: export the Development schema, hand-write the `RECORD TYPE` block mirroring an existing one for field types (SwiftData maps UUID → STRING, Int/Bool → INT64, Date → TIMESTAMP, `[Date]?` → BYTES, plus `CD_entityName STRING QUERYABLE SEARCHABLE SORTABLE` and the same GRANTs), then: ```bash xcrun cktool validate-schema ... --environment development --file new.ckdb xcrun cktool import-schema ... --environment development --file new.ckdb ``` `import-schema` **refuses Production** ("endpoint not applicable in the environment 'production'") and cktool has **no deploy/promote command** — the Development → Production step is console-only, by Apple's design: CloudKit Console → container → Development → *Deploy Schema Changes…*. A human has to click it. Verify afterwards with `export-schema --environment production` and diff it against development. Production schema changes are **permanent and additive**: you can add a record type or field, never remove one or change a field's type. ## Running it in the simulator ```bash SIM=$(xcrun simctl list devices available | grep -m1 "iPhone 16 Plus" | grep -oE "[0-9A-F-]{36}") open -a Simulator xcodebuild -scheme continuum -configuration Debug \ -destination "platform=iOS Simulator,id=$SIM" -derivedDataPath /tmp/dd build xcrun simctl install $SIM /tmp/dd/Build/Products/Debug-iphonesimulator/continuum.app xcrun simctl launch $SIM orion-labs.continuum xcrun simctl io $SIM screenshot shot.png ``` **Always look at the screenshot.** A green build proves nothing about layout, z-order, or fonts — three real bugs in the 3.4 celebration work were invisible to the compiler and obvious on screen. ### Driving the UI (taps) `simctl` has no tap command. Use `cliclick` against the Simulator window: ```bash osascript -e 'tell application "System Events" to tell process "Simulator" \ to get {position, size} of window 1' # e.g. 1174, 50, 484, 1030 ``` Device origin = window origin + **27pt bezel**, plus **44pt titlebar** on y. For a 430x932pt device in a 484x1030 window at (1174,50): `screen_x = 1201 + pt_x`, `screen_y = 121 + pt_y`, and `pt = screenshot_px / 3`. ```bash osascript -e 'tell application "Simulator" to activate' # REQUIRED before each batch sleep 1 cliclick c:1233,196 # tap cliclick dd:1311,559; sleep 2; cliclick du:1311,559 # hold-to-complete gesture ``` Gotchas: clicks are silently dropped unless the window was just activated, and `kp:esc` does **not** dismiss a SwiftUI context menu — tap empty space instead. ### Xcode 27: no Simulator.app, use DeviceHub (2026-09-29) Xcode 27 (installed locally, macOS 26.6) ships `DeviceHub.app` in `/Applications/Xcode.app/Contents/Applications/` instead of Simulator.app. Booted simulators run headless — `simctl io … screenshot` and `simctl launch` work without it — but to tap you open DeviceHub, select the device in its sidebar, and click the live screen it shows. `xcodebuild` wants the device by id (`-destination 'platform=iOS Simulator,id=…'`); the name alone was ambiguous across the iOS 18.6 and 27.0 runtimes. With the window at (314,175) 1100x800 and the 16 Pro Max at DeviceHub's default zoom: `screen_x = 838.4 + 0.681 * pt_x`, `screen_y = 253.8 + 0.682 * pt_y`. Re-measure from a `screencapture` of the window if it moves. Roughly one click in two is dropped, most often the first after a relaunch or an `activate`. A tap on empty space first helps, but check every step with a screenshot. Menus (the card's ⋯, the share sheet) often need the item tapped twice. The home-screen widget gallery never accepted the "Add Widget" tap; it's easier to check widget layout another way. **Prefer launch switches to DeviceHub taps.** The 2026-09-30 screenshots took no clicks. Temporary env-gated code, reverted after: `HARNESS_COMPLETE=<habit>` in ContentView's `onAppear` marks the habit done and calls `checkForMilestones` (the comeback tile, or graduation after `seedForGraduationScreenshot`); `HARNESS_HOLD=<habit>` in HabitCardView sets `isAnimatingCompletion`, `completionProgress = 0.5`, `cardScale = 0.97`; `TileCelebration.duration = 120` keeps a tile up. Launch with `SIMCTL_CHILD_HARNESS_…=… xcrun simctl launch`, capture with `simctl io`. Commit the real work first so `git checkout` strips the switches cleanly. The stats sheet works the same way: present `HabitStatsView` in a `.sheet` over `ContentView` from a harness in `continuumApp.swift`. **Widgets without the home screen:** copy `continuumWidget/continuumWidget.swift` into `continuum/` (no `@main` there, no name clashes) and render `SmallWidgetView` at 170x170 and `MediumWidgetView` at 364x170, each with `.padding(16)` for the content margins and `.fontDesign(.monospaced)`. That's how the medium widget's overflow turned up. Delete the copy after. **Never `screencapture -R` the DeviceHub rectangle** to check its state: other apps' windows share that rectangle, and one capture caught a private Messages thread. `simctl io … screenshot` sees only the simulator.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen