From 22bece8a2b33e071e492f02ea9d064f09b173bad Mon Sep 17 00:00:00 2001 From: Arul Sharma <31745423+arul28@users.noreply.github.com> Date: Tue, 18 Aug 2026 23:39:44 -0400 Subject: [PATCH] fix(ios): make the ADE scheme build again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The iOS app is not built by PR CI, and three recent merges each landed a compile break that the next one hid: - WorkSessionDestinationView passed onOpenParentSession right after onOpenLane, but memberwise-init argument order follows property declaration order in WorkChatSessionView, where it sits after the model controls (#1117). - SyncService.errorByClearingAmbiguousRouteAuthFailure is called from the connection race's task-group closures off the main actor; it is a pure NSError rewrite, so mark it nonisolated (#1120). - WorkChatSessionView.body had grown into one ~300-line expression chain; the x86_64 simulator slice hit the type-checker's "unable to type-check in reasonable time" ceiling (#1121). Split it into bounded helpers (transcriptScrollView / chatColumn / timelineScrollHandlers / sessionLifecycleHandlers / feedbackAndSheets) with the identical view tree and modifier order. Verified: xcodebuild build-for-testing succeeds for the ADE scheme (simulator, both arches); ADETests runs 1494 tests with 6 failures that predate this branch (PR-list workflow context, roster delta, sync recovery policy — tracked separately). Co-Authored-By: Claude Fable 5 --- apps/ios/ADE/Services/SyncService.swift | 4 +- .../ADE/Views/Work/WorkChatSessionView.swift | 45 +++++++++++++++++-- .../Work/WorkSessionDestinationView.swift | 5 ++- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/apps/ios/ADE/Services/SyncService.swift b/apps/ios/ADE/Services/SyncService.swift index c199f7e72..c0b7996c4 100644 --- a/apps/ios/ADE/Services/SyncService.swift +++ b/apps/ios/ADE/Services/SyncService.swift @@ -16113,7 +16113,9 @@ final class SyncService: ObservableObject { return NSError(domain: nsError.domain, code: nsError.code, userInfo: userInfo) } - private func errorByClearingAmbiguousRouteAuthFailure(_ error: Error) -> Error { + // Pure NSError rewrite with no actor state; nonisolated so the connection + // race's task-group closures can call it off the main actor. + private nonisolated func errorByClearingAmbiguousRouteAuthFailure(_ error: Error) -> Error { let nsError = error as NSError var userInfo = nsError.userInfo userInfo[syncAmbiguousRouteAuthFailureKey] = false diff --git a/apps/ios/ADE/Views/Work/WorkChatSessionView.swift b/apps/ios/ADE/Views/Work/WorkChatSessionView.swift index 39850ff68..07acab1cd 100644 --- a/apps/ios/ADE/Views/Work/WorkChatSessionView.swift +++ b/apps/ios/ADE/Views/Work/WorkChatSessionView.swift @@ -1300,9 +1300,12 @@ struct WorkChatSessionView: View { .padding(.bottom, 0) } - var body: some View { - ScrollViewReader { proxy in - VStack(spacing: 0) { + /// Extracted from `body` so the type-checker sees two bounded + /// expressions instead of one ~300-line chain; the x86_64 simulator + /// slice hit the "unable to type-check in reasonable time" ceiling + /// on the combined expression. + @ViewBuilder + private func transcriptScrollView(proxy: ScrollViewProxy) -> some View { ScrollView { LazyVStack(alignment: .leading, spacing: 14) { sessionOverviewSection @@ -1408,6 +1411,15 @@ struct WorkChatSessionView: View { .transition(.move(edge: .trailing).combined(with: .opacity)) } } + } + + /// Layout half of the chat column (structure + geometry preferences). + /// Split from `body` so the type-checker sees bounded expressions; the + /// behavior chain (onChange/sheet/task) stays in `body`. + @ViewBuilder + private func chatColumn(proxy: ScrollViewProxy) -> some View { + VStack(spacing: 0) { + transcriptScrollView(proxy: proxy) composerInset(proxy: proxy) .fixedSize(horizontal: false, vertical: true) @@ -1478,6 +1490,11 @@ struct WorkChatSessionView: View { olderHistoryTriggerArmed = false requestEarlierTimelineEntries(automatically: true) } + } + + /// Timeline/scroll change handlers, split from `body` for type-checker budget. + private func timelineScrollHandlers(_ content: V, proxy: ScrollViewProxy) -> some View { + content .onChange(of: timeline.count) { oldCount, newCount in let previousTailId = lastTimelineTailId lastTimelineTailId = timeline.last?.id @@ -1525,6 +1542,11 @@ struct WorkChatSessionView: View { unreadBelowCount = 0 } } + } + + /// Session lifecycle + input-recovery handlers, split from `body` for type-checker budget. + private func sessionLifecycleHandlers(_ content: V, proxy: ScrollViewProxy) -> some View { + content .onAppear { prepareScrollStateForCurrentSessionIfNeeded(reason: "appear") if transcript.isEmpty && fallbackEntries.isEmpty { @@ -1610,6 +1632,11 @@ struct WorkChatSessionView: View { .onChange(of: liveClaudeQuotaCardId) { _, newId in handleLiveQuotaCardChange(newId) } + } + + /// Haptics and sheet presenters, split from `body` for type-checker budget. + private func feedbackAndSheets(_ content: V) -> some View { + content .sensoryFeedback(.impact(weight: .light), trigger: blockingPendingHapticToken) .sensoryFeedback(.impact(weight: .light), trigger: quotaCardHapticToken) .sheet(isPresented: $artifactDrawerPresented) { @@ -1663,8 +1690,18 @@ struct WorkChatSessionView: View { .presentationDragIndicator(.visible) } } - } + } + + var body: some View { + ScrollViewReader { proxy in + feedbackAndSheets( + sessionLifecycleHandlers( + timelineScrollHandlers(chatColumn(proxy: proxy), proxy: proxy), + proxy: proxy + ) + ) } + } } private extension WorkChatSessionView { diff --git a/apps/ios/ADE/Views/Work/WorkSessionDestinationView.swift b/apps/ios/ADE/Views/Work/WorkSessionDestinationView.swift index 4ab2077b1..be4025c5c 100644 --- a/apps/ios/ADE/Views/Work/WorkSessionDestinationView.swift +++ b/apps/ios/ADE/Views/Work/WorkSessionDestinationView.swift @@ -1559,7 +1559,6 @@ struct WorkSessionDestinationView: View { inputLockMessage: inputLockMessage, transitionNamespace: transitionNamespace, onOpenLane: openLaneAction, - onOpenParentSession: viewingSubagent ? nil : { openParentSession() }, onSend: { text, attachments, mode in await sendMessage(text, attachments: attachments, deliveryMode: mode) }, @@ -1585,6 +1584,10 @@ struct WorkSessionDestinationView: View { onSelectRuntimeMode: selectRuntimeMode, onSelectEffort: selectReasoningEffort, onSelectCodexFastMode: selectCodexFastMode, + // Memberwise-init argument order follows property declaration order in + // WorkChatSessionView, where onOpenParentSession sits after the model + // controls. + onOpenParentSession: viewingSubagent ? nil : { openParentSession() }, resolvedSessionStatus: resolvedSessionStatus, lanes: lanes, lanesRenderSignature: workLaneListRenderSignature(lanes),