← back to dashboard

SynCal — Fix-Ready Issue

_Regenerated 2026-08-03. Every claim below was re-verified this cycle against live state and the live working tree: asc-live.json ("SynCal": false), asc-review.json (UNRESOLVED_ISSUES, 1 error / 1 blocking, submission 01ab9848-0044-4961-8139-f66e6d611514), asc.json (last known-good build 4, VALID), fleet.json (build 11 held, in_flight: pending_review, last_build_status: success), run log SynCal-2026-07-28T20-59-52Z.log (339 lines, zero error: lines), and the sources listed below. git log -1 = da67bbf; git status --short = single ?? SynCal/LoginView.swift; CURRENT_PROJECT_VERSION = 11 at project.pbxproj:274 and :320._

Status (one line): UNHEALTHY (state/asc-live.json: "SynCal": false) — not auto-healed because there is nothing for the pipeline to fix: the build already succeeds, the blocker is an unresolved Apple App Review rejection (Guideline 4.8) against build 4 that can only be cleared by a human uploading a new build and resubmitting in App Store Connect (diagnosis: "Not auto-healing (fixable=false, autoHeal=true)"; verify: isRealFix=false, confidence=high, "No fix attempted").


Last known-good build

Build 41.0.0 (4), state VALID, uploaded 2026-06-10T09:07:22-07:00. From /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apple-apps-fleet/state/asc.jsonapps.SynCal:

"SynCal": {
  "bundle_id": "com.zzxwill.SynCal",
  "asc_id": "6774228993",
  "asc_name": "SynCal - Calendar Sync",
  "in_prod": true,
  "latest_build_number": "4",
  "latest_build_state": "VALID",
  "latest_uploaded": "2026-06-10T09:07:22-07:00",
  "build_count": 3,
  "app_store_state": "REJECTED",
  "version_string": "1.0.0"
}

Caveat: build 4 is "last known-good" only in the sense that it is the newest build App Store Connect actually holds in a VALID state. It is also the build Apple rejected (app_store_state: REJECTED). Builds 5–11 were built locally and held for review; none were ever uploaded to ASC.


Root cause

The blocker is a review rejection, not a build failure

The build is healthy. The run log ends cleanly:

** ARCHIVE SUCCEEDED **
...
** EXPORT SUCCEEDED **
verify-ipa: IPA CFBundleVersion=11, project CURRENT_PROJECT_VERSION=11
verify-ipa: signing authority=Apple Distribution: Zhengxi Zhou (94479PZ7X4)
verify-ipa: OK — build 11, distribution-signed
[05:00:14] BUILD READY — held for your review (build 11)
[05:00:14] to publish to ASC after review:  fleet publish SynCal
[05:00:19] done (awaiting publish approval)

(/Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apple-apps-fleet/state/runs/SynCal-2026-07-28T20-59-52Z.log)

The app is unhealthy solely because asc-review.json still reports an open blocking issue against 1.0.0 (4).

The failing log line (App Review message, state/asc-review.jsonapps.SynCal.message.latest.body)

Review date: June 15, 2026
Review Device: iPhone 17 Pro Max
Version reviewed: 1.0.0 (4)

Guideline 4.8 - Design - Login Services

Issue Description

The app uses a third-party login service, but does not appear to offer as an
equivalent login option another login service with all of the following features:

- The login option limits data collection to the user's name and email address.
- The login option allows users to keep their email address private from all
  parties as part of setting up their account.
- The login option does not collect interactions with the app for advertising
  purposes without consent.

Next Steps

Revise the app to offer as an equivalent login option another login service that
meets all of the above requirements.

nextAction: "Resolve the outstanding App Review issues in App Store Connect, then resubmit if needed."

Key finding: the 4.8 fix is already written but has never reached Apple

Sign in with Apple is fully implemented in the working tree and was not present in build 4:

So the code answer to Guideline 4.8 exists in build 11. Build 11 was never uploaded (fleet.json: last_upload_status: held_for_review, pending_review_build: 11, held since 2026-07-28T21:00:14Z). Apple is still judging build 4, which lacked SIWA. This is why the app stays red: the fix is sitting on disk, unshipped, behind a manual publish gate.

Secondary defects that should be cleaned up before publishing build 11

(a) Root view gates on a shadow flag that ignores restored auth state.
SynCal/SynCal/SynCalApp.swift:10 declares @AppStorage("hasCompletedLogin") private var hasCompletedLogin = false, and l.28–38 route the entire app on it. Both services already restore their sessions on launch (AppleSignInService.swift:28restore() sets isSignedIn = true at l.39; GoogleCalendarService.swift:69 sets isSignedIn = (refreshToken != nil) || (accessToken != nil)), but the root view reads neither. grep -rn 'hasCompletedLogin' SynCal/ returns exactly four lines, all in SynCalApp.swift (10, 28, 30, 36) — nothing else reads or writes it.

(b) Sign-out strands the user. Views/SettingsView.swift:134 calls appleSignInService.signOut() and l.503 calls googleCalendarService.signOut(); both clear isSignedIn. Nothing clears hasCompletedLogin, so the user stays inside ContentView while signed out, with no route back to LoginView.

(c) LoginView advances even when Apple sign-in fails or is cancelled. Views/LoginView.swift:44–51 calls onCompleted() from an unconditional 0.3 s DispatchQueue.main.asyncAfter, ignoring appleSignInService.isSignedIn:

} onCompletion: { result in
    isSigningInWithApple = true
    appleSignInService.handle(result)
    // Brief delay to ensure UI updates before transitioning
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
        onCompleted()
    }
}

A reviewer who taps "Sign in with Apple" and cancels lands in the app signed-out — plausibly what turns a 4.8 re-review into another rejection.

(d) Stray empty duplicate source file. SynCal/SynCal/LoginView.swift is 0 bytes and untracked (?? SynCal/LoginView.swift). project.pbxproj:47 references LoginView.swift by bare path with sourceTree = "<group>", so the group's directory determines which file compiles. The real view is SynCal/SynCal/Views/LoginView.swift (6928 bytes). The empty twin is a live footgun that can silently blank the login screen on a project re-resolve.


Claude-ready fix prompt

> You are fixing the iOS app SynCal at /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal. It is blocked by an App Store rejection under Guideline 4.8 — Design — Login Services against build 1.0.0 (4). Sign in with Apple is already implemented and entitled in the working tree (build 11) but that build was never uploaded. Your job is to remove the remaining login-flow defects so build 12 can be uploaded and the 4.8 rejection answered. Do not change anything unrelated to auth/login routing.
>
> 1. Delete the stray empty duplicate source file.
> rm /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/LoginView.swift (0 bytes, untracked). Keep SynCal/SynCal/Views/LoginView.swift. Then confirm SynCal.xcodeproj/project.pbxproj (refs at lines 27, 47, 103, 213) still resolves LoginView.swift to the Views/ copy; build to verify the login screen is not blank.
>
> 2. Delete the hasCompletedLogin shadow flag; derive routing from real auth state.
> In SynCal/SynCal/SynCalApp.swift: remove the @AppStorage("hasCompletedLogin") declaration at l.10 and rewrite the Group at l.27–39 to branch on the services instead. Add @StateObject-observed state, e.g.:
> ```swift
> private var isAuthenticated: Bool {
> appleSignInService.isSignedIn || googleCalendarService.isSignedIn
> }
> ```
> then route if isAuthenticated && hasCompletedSetup { ContentView() } else if isAuthenticated { CalendarSetupView { ... } } else { LoginView { } }. Keep hasCompletedSetup as-is — it is genuine onboarding state, not auth state. Because both services publish isSignedIn and are @StateObjects already held by SynCalApp, SwiftUI re-renders on sign-in and sign-out automatically. This single change fixes both the "forced re-login on every cold launch" bug and the "sign-out strands the user" bug (Views/SettingsView.swift:134 and :503) with no edits to SettingsView.
>
> 3. Make LoginView only advance on actual success.
> In SynCal/SynCal/Views/LoginView.swift:44–51, delete the unconditional DispatchQueue.main.asyncAfter and gate the transition on the result. AppleSignInService.handle(_:) (Services/AppleSignInService.swift:58) already sets isSignedIn; make the completion handler check result / appleSignInService.isSignedIn and call onCompleted() only when signed in, surfacing failures through the existing error Text at l.104–110 (rename/reuse googleSignInError or add a parallel appleSignInError). Also reset isSigningInWithApple = false on the failure path. If step 2 is done, onCompleted becomes a no-op and can be dropped entirely — prefer that.
>
> 4. Verify, then bump and build.
> Cold-launch checks on simulator or device: (a) sign in with Apple, force-quit, relaunch → lands in ContentView, not LoginView; (b) sign out from Settings → returns to LoginView; (c) tap "Sign in with Apple" and cancel → stays on LoginView with an error, never enters the app; (d) same three for Google. Then bump CURRENT_PROJECT_VERSION from 11 to 12 at SynCal.xcodeproj/project.pbxproj:274 and :320 (both build configs — leave MARKETING_VERSION = 1.0.0 at :301/:347 alone), and run make export in the app directory.
>
> 5. Human-only step — do not attempt to automate.
> Upload the build (fleet publish SynCal), attach 1.0.0 (12) to the version in App Store Connect, then reply to App Review on submission 01ab9848-0044-4961-8139-f66e6d611514 stating that the app now offers Sign in with Apple as an equivalent login option alongside Google, and resubmit. The app stays asc-live: false until a human does this; no code change alone will clear it.


Reference — files involved

| Path | Role |
|---|---|
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/SynCalApp.swift | Root routing; hasCompletedLogin shadow flag (l.10, 28, 30, 36) |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/Views/LoginView.swift | Real login screen; premature onCompleted() at l.44–51 |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/LoginView.swift | Delete — 0-byte untracked duplicate |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/Services/AppleSignInService.swift | SIWA service; restores session at l.26–29 |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/Services/GoogleCalendarService.swift | Google auth; isSignedIn derived at l.69 |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/Views/SettingsView.swift | Sign-out entry points (l.134, l.503) |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal/SynCal.entitlements | com.apple.developer.applesignin present — no change needed |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SynCal/SynCal.xcodeproj/project.pbxproj | CURRENT_PROJECT_VERSION at l.274 / l.320 |
| /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apple-apps-fleet/state/runs/SynCal-2026-07-28T20-59-52Z.log | Successful build-and-hold run log |