feat: iOS Companion App & iCloud KVS Sync#1359
Conversation
Adds an iOS Companion App and switches the macOS app's device-to-device sync to iCloud Key-Value Store (KVS), removing the previous Tailscale/HTTP tunnel entirely. ### iOS Companion App - Created `CodexBarCompanion`, a lightweight SwiftUI iOS application. - Mirrors the macOS menubar usage stats, charts, and progress bars. - Reads its data exclusively from iCloud KVS (no local network dependency). - Adjustable refresh interval (1–10 min) via the clock control in the header. ### iCloud KVS Sync - macOS serializes the `CompanionCardModel` payload into `NSUbiquitousKeyValueStore` on every refresh (`StatusItemController+Companion`). - iOS observes `didChangeExternallyNotification` and reads the same key, so updates propagate automatically across the user's Apple devices. - The KVS identifier is `$(TeamIdentifierPrefix)com.steipete.codexbar`, shared by both the macOS app entitlement and the iOS companion so they address the same container. ### UI & Architecture Improvements - **Empty States**: `StatusItemController` and `MenuCardView` automatically hide providers that have no data or are not logged in (e.g. Gemini "not logged in"), saving screen space. - **iCloud-only**: removed the local HTTP server, the Tailscale IP entry field, and the cleartext-HTTP fallback (along with its ATS exception). The companion entitlement is now KVS-only — no CloudKit/APS containers. ### Build Script Enhancements - `package_app.sh` injects `com.apple.application-identifier`, `com.apple.developer.team-identifier`, and `com.apple.developer.ubiquity-kvstore-identifier` from the script's resolved `$APP_TEAM_ID` and `$BUNDLE_ID`. - Optionally embeds a local `codexbar-macos.provisionprofile` (gitignored) if present at the repo root, to satisfy AMFI validation for iCloud entitlements during local debug builds. ###⚠️ IMPORTANT: Instructions for Fork Owners To compile this project locally with iCloud KVS support, you must: 1. Register your App ID with the **iCloud (Key-Value Store)** capability in the Apple Developer Portal. 2. Generate a **macOS App Development** provisioning profile for that App ID. 3. Save it as `codexbar-macos.provisionprofile` in the repo root (gitignored; the build script auto-detects and embeds it). 4. Keep only ONE matching development certificate in your Keychain to avoid `codesign` ambiguity. 5. Build with `APP_TEAM_ID` set to your own team so the macOS app and the iOS companion sign under the same team and share the same KVS container.
|
Codex review: needs real behavior proof before merge. Reviewed June 9, 2026, 7:20 PM ET / 23:20 UTC. Summary Reproducibility: not applicable. this is a new iOS companion and iCloud sync feature, not a reported current-main bug. The gating evidence is real signed behavior proof rather than a bug reproduction path. Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Land only after iOS and macOS share a configurable signing/KVS contract, the selected-provider update edge case is fixed, and maintainers approve the privacy/release path with redacted live-sync proof. Do we have a high-confidence way to reproduce the issue? Not applicable; this is a new iOS companion and iCloud sync feature, not a reported current-main bug. The gating evidence is real signed behavior proof rather than a bug reproduction path. Is this the best way to solve the issue? Unclear; iCloud KVS is a plausible narrow transport, but the current branch still has signing/config drift and no live signed KVS proof, so maintainers cannot yet treat it as the best release path. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against a4f278d91fe4. Label changesLabel justifications:
Evidence reviewedSecurity concerns:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d363a523a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| withObservationTracking { | ||
| _ = self.store.menuObservationToken | ||
| } onChange: { [weak self] in | ||
| Task { @MainActor [weak self] in |
There was a problem hiding this comment.
Sync companion after settings changes
Because the companion payload is refreshed only from the store-observation path and startup, settings-only changes that affect the rendered card never update iCloud KVS. For example, toggling hidePersonalInfo or disabling/reordering providers is handled by handleSettingsChange() and redraws the macOS menu, but the previously synced payload can keep exposing the old email/plan/provider list on the iOS companion until a later usage refresh happens. Please also call syncCompanion() from the settings-change path that rebuilds/invalidate menus.
Useful? React with 👍 / 👎.
…ilter Responds to the ClawSweeper/Codex review on steipete#1359: - [P1] iCloud companion sync is now OPT-IN (off by default) via a new `companionSyncEnabled` setting (Preferences → Advanced). When disabled, syncCompanion() clears any previously-synced KVS payload so upgrades never leave usage data in iCloud. Email is omitted from the payload when `hidePersonalInfo` is enabled (data minimization). - [P1] package_app.sh no longer injects iCloud entitlements into every build. They are gated behind CODEXBAR_ICLOUD_COMPANION=1; default/local/adhoc/fork/ release builds are unchanged and need no Apple iCloud App ID or profile. - [P2] iOS reader keeps content-bearing cards (cost/notes/credits/placeholder) instead of dropping everything without quota metrics (hasDisplayableContent). - [P2] syncCompanion() is now also called from handleSettingsChange(), so toggling hidePersonalInfo / hiding / reordering providers refreshes the companion payload instead of leaving stale data on iOS. Proof: adds CompanionSyncProofTests (JSON round-trip + content filter) and os_log markers on the macOS writer and iOS reader for live KVS verification.
|
Thanks for the thorough review — all four findings addressed in Review findings
ProofUnit proof — the KVS value is plain JSON, so this verifies the macOS-writer → iOS-reader round-trip and the content filter ( Live-sync observability — added
To reproduce end-to-end (opt-in on, same iCloud account on both devices): enable Preferences → Advanced → “Sync to iOS app (iCloud)”, trigger a refresh on macOS, and both log lines fire in order. I'll attach a redacted screen recording of that path once I have both devices on the same signed build. Builds: macOS @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
…cales) - Add CompanionSyncProofTests.optInDefaultsOff: a fresh SettingsStore returns companionSyncEnabled == false, directly proving the privacy default. - Add companion_sync_title/subtitle to the remaining 9 locales (es, fr, ca, nl, sv, vi, uk, zh-Hans, zh-Hant) so non-English builds don't fall back to English.
Resolve conflict in StatusItemController.swift: upstream refactored the store-change observation callback into handleObservedStoreMenuChange(). Re-applied the companion sync by calling self.syncCompanion() inside that new method (right after observeStoreChanges()), preserving the PR's intent of refreshing the iOS companion KVS payload on every usage refresh.
Addresses ClawSweeper [P2] (CompanionCardView.swift:38-46): hasDisplayableContent keeps cards that only have providerCost / tokenUsage / creditsText (no metrics), but the view's body branched on metrics.isEmpty and only rendered usageNotes or a placeholder in that case — so cost-only / credits-only providers showed a blank header + divider with the actual details missing. Hoist hasUsage/hasCredits/hasCost out of the metrics-empty branch and render the full content block whenever any of them is present; fall back to the placeholder only when there is no content at all. Also include creditsText in hasDetails so credits-only cards get the header divider/padding for consistency.
|
Rebased onto latest Merge conflict resolved
Review finding
Verification
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Adds an iOS Companion App and switches the macOS app's device-to-device sync to iCloud Key-Value Store (KVS), removing the previous Tailscale/HTTP tunnel entirely.
iOS Companion App
CodexBarCompanion, a lightweight SwiftUI iOS app.iCloud KVS Sync
CompanionCardModelpayload intoNSUbiquitousKeyValueStoreon every refresh (StatusItemController+Companion).didChangeExternallyNotificationand reads the same key, so updates propagate automatically across the user's Apple devices.$(TeamIdentifierPrefix)com.steipete.codexbar, shared by both the macOS app entitlement and the iOS companion so they address the same container.UI & Architecture
StatusItemController/MenuCardViewhide providers with no data or that are logged out (e.g. Gemini "not logged in").Build script
package_app.shinjectscom.apple.application-identifier,com.apple.developer.team-identifier, andcom.apple.developer.ubiquity-kvstore-identifierfrom the resolved$APP_TEAM_ID/$BUNDLE_ID.codexbar-macos.provisionprofile(gitignored) for AMFI validation of iCloud entitlements during local debug builds.Fork owners
To build with iCloud KVS support:
codexbar-macos.provisionprofilein the repo root (gitignored; auto-embedded by the build script).codesignambiguity.APP_TEAM_IDset to your own team so the macOS app and the companion share the same KVS container.Verification
swift build): ✅ build complete.xcodebuild, iOS 26 simulator): ✅ build succeeded.Screenshots — iOS Companion