Skip to content

feat(ios): add experimental SwiftUI client - #5178

Open
t3dotgg wants to merge 137 commits into
mainfrom
t3code/rebuild-mobile-app-swift
Open

feat(ios): add experimental SwiftUI client#5178
t3dotgg wants to merge 137 commits into
mainfrom
t3code/rebuild-mobile-app-swift

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 1, 2026

Copy link
Copy Markdown
Member

T3 Code's shipped mobile client is React Native. This experiment adds a standalone native SwiftUI client so the team can try its feel, performance, and connection workflows without replacing any existing surface.

The app lives entirely in apps/swift-ios, speaks the existing server contracts directly, and installs side by side as T3 Code (SwiftUI) with bundle ID com.t3tools.t3code.swiftui.

Try it

  1. Open apps/swift-ios/T3Code.xcodeproj in Xcode.
  2. Select the T3Code scheme and an iOS 17+ simulator or device.
  3. Build and pair with an existing T3 Code server using its URL and code, pairing link, or QR code.

See apps/swift-ios/README.md for architecture, included functionality, and known gaps.

What to test

  • Pairing, onboarding, and multiple environments
  • Web V2 home behavior with large thread collections
  • Message-first thread creation and model selection
  • Long Markdown transcripts, composer states, approvals, and input requests
  • Image attachments, reconnect behavior, project tools, and terminal sessions

Preview

Home Thread
SwiftUI home with a large thread collection SwiftUI long Markdown thread

Verification

  • 245 native simulator tests passed, 0 failed, 1 skipped

  • Repeated A to B to C to A long-thread navigation verified against an isolated real-data snapshot

  • Latest build compiled, installed, and launched on an iPhone 17 Pro simulator

  • Signed latest build installed on Big O and DevPhone15; automatic launch deferred because both devices were locked

  • Remove DO NOT MERGE only after explicit maintainer approval

This PR was built by GPT-5.6-sol using the Codex harness in T3 Code.


Note

High Risk
Large new iOS surface with Clerk, DPoP, relay token exchange, and Keychain-backed crypto on security-critical connection paths; experimental and parallel to the shipped React Native client.

Overview
Introduces an experimental native SwiftUI iOS client under apps/swift-ios that installs side by side with the existing React Native app and talks to T3 servers directly (not via packages/client-runtime).

The diff adds a T3 Connect stack for managed environments: Clerk session + relay JWT (T3ConnectClerkSession), P-256 DPoP proofs with a device-bound Keychain identity (T3ConnectDPoPSigner), relay HTTP APIs (T3ConnectRelayClient), bootstrap-to-access-token exchange and WebSocket ticketing (T3ConnectManagedEnvironmentAuthorizer), and a T3ConnectController / T3ConnectRuntimeAuthorization bridge into the app’s environment credential model. Build-time Connect settings are resolved from Info.plist keys in T3ConnectConfiguration.

Contributor and agent docs are updated so mobile is two clients: React Native (apps/mobile, Metro/Expo) vs SwiftUI (apps/swift-ios, Xcode T3Code scheme, t3code-swiftui-dev deep links). AGENTS.md, test-t3-mobile, test-t3-app, and ios-debugger-agent now require picking the affected client before verification.

Reviewed by Cursor Bugbot for commit 7b8bb94. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add experimental native SwiftUI iOS client with full workspace, terminal, and T3 Connect support

  • Introduces a complete SwiftUI iOS app in apps/swift-ios as an alternative to the React Native client, with its own Xcode project, CI scripts, and README.
  • Implements a WebSocketRPCClient actor and T3Client that handle WebSocket connections, HTTP polling fallback, RPC request/response framing, and credential management via Keychain.
  • Adds full workspace UI: thread list with diffable UIKit collection view, thread detail with Markdown rendering and streaming cache, file browser, diff/review view, source control view, and a Ghostty-based terminal surface.
  • Adds T3 Connect managed-DPoP authentication flow using Clerk, a relay API client, and a T3ConnectManagedEnvironmentAuthorizer for token exchange and WebSocket ticketing.
  • Adds a Share extension, a WidgetKit extension (Recent Tasks widget + Live Activity), Siri App Shortcuts, background refresh, haptic feedback, push notification handling, and deep link parsing.
  • Includes a comprehensive test suite covering core contracts, RPC race conditions, pairing, DPoP cryptography, workspace models, platform agent awareness, deep links, and incoming share pipelines.
  • Risk: this is explicitly experimental; it introduces new build targets, entitlements, and third-party frameworks (ClerkKit, GhosttyKit) that are not part of the existing React Native pipeline.

Macroscope summarized 7b8bb94.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a29eef94-8dc2-4ac1-96af-7c26e76626d5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@t3dotgg t3dotgg added DO NOT MERGE Experimental pull request. Do not merge. enhancement Requested improvement or new capability. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 1, 2026
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment thread apps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment thread apps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment thread apps/swift-ios/Core/JSONValue.swift
Comment thread apps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment thread apps/swift-ios/Features/Connection/ConnectionOnboardingView.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Core/T3Client.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeapp Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

@t3dotgg

t3dotgg commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Provider logo follow-up

Replaced the generic CPU and initial badges with the repository's official OpenAI, Claude, Cursor, Grok, and OpenCode SVG artwork. The same driver-keyed component now covers model rows, model configuration, settings/new-task controls, and the compact composer trigger. Unknown custom providers retain the initial fallback.

Before After
Model picker before, using initial badges Model picker after, using the official Claude logo

OpenAI and OpenCode rendering:

Official OpenAI and OpenCode marks in the SwiftUI model picker

Verification:

  • Focused model-picker tests: 8 passed, 0 failed
  • Simulator build and visual inspection passed
  • Signed build installed and launched on DevPhone15

Commit: 8792d15f5

@t3dotgg

t3dotgg commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Mobile interaction polish

This pass replaces the generic home-row sparkle with the resolved harness mark, keeps the latest transcript content visible when the software keyboard changes the viewport, makes keyboard dismissal immediate, constrains long thread headers, and reduces mobile prompt controls to model + reasoning in the composer and Automatic / Full access in the thread menu.

Previous home Harness-aware home
Previous SwiftUI home SwiftUI home with harness icons
Previous thread Keyboard-pinned composer
Previous SwiftUI thread SwiftUI thread pinned above the software keyboard

Verification:

  • 23 focused simulator tests passed, 0 failed
  • Simulator build, software-keyboard transition, menu contents, title constraints, and composer layout inspected
  • Signed 04ccb7a9c build installed and launched on DevPhone15

Commits: 5ab663449 through 04ccb7a9c

Comment thread apps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Features/Workspace/HomeThreadCollectionView.swift
Comment thread apps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment thread apps/swift-ios/Core/T3Client.swift
Comment thread apps/swift-ios/Features/Workspace/ProjectCreationModels.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Features/Workspace/ProjectAndArchiveViews.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment thread apps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Core/T3Client.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Features/Workspace/NewThreadView.swift
Comment thread apps/swift-ios/Features/Workspace/NewThreadView.swift
Comment thread apps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment thread apps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment thread apps/swift-ios/Features/Workspace/NewThreadView.swift
Comment thread apps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment thread apps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment thread apps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment thread apps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment thread apps/swift-ios/Features/Files/FeatureFilesView.swift
let resolvedURL = try await resolver.workspaceAssetURL(
threadID: threadID,
path: entry.path
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image preview resolver never wired

High Severity

Image file previews cast the client to FeatureWorkspaceAssetResolving, but NativeFeatureClient never conforms to that protocol or implements workspaceAssetURL. Opening any workspace image hits the capability-unavailable path every time, even though T3Client.resolvedAssetURL and AssetResource.workspaceFile already exist for this route.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0096041. Configure here.

Comment thread apps/swift-ios/Core/WebSocketRPC.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Features/Root/FeatureRootModel.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/Core/WebSocketRPC.swift
Comment thread apps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment thread apps/swift-ios/Features/Root/FeatureRootModel.swift
Comment thread apps/swift-ios/Features/Root/FeatureRootModel.swift
Comment thread apps/swift-ios/Resources/Info.plist Outdated
Comment thread apps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment thread apps/swift-ios/Features/Settings/SettingsView.swift
Comment thread apps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment thread apps/swift-ios/Features/Settings/SettingsView.swift
Comment thread apps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment thread apps/swift-ios/Scripts/ci-test.sh
Comment thread apps/swift-ios/Core/T3Client.swift Outdated
Comment thread apps/swift-ios/App/NativeFeatureClient.swift
Comment thread apps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment thread apps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment thread apps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment thread apps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment thread apps/swift-ios/App/Platform/PlatformCloudDelivery.swift
Comment thread apps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment thread apps/swift-ios/Features/Root/FeatureRootModel.swift
Comment thread apps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment thread apps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment thread apps/swift-ios/Scripts/resolve-device-udid.swift Outdated
var index = 2
while occupied.contains("term-\(index)") { index += 1 }
return "term-\(index)"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong primary terminal ID scheme

High Severity

TerminalSessionList treats "default" as the primary shell and allocates new IDs as term-2, term-3, …, skipping term-1. Shared contracts and other clients use term-1 via DEFAULT_TERMINAL_ID / nextTerminalId, so this client opens a separate shell, can label both as Terminal 1, and fails to share the primary session with web or React Native.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ff13ebd. Configure here.

cwd: context.cwd,
worktreePath: context.worktreePath,
columns: 80,
rows: 24

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attach forces 80×24 resize

Medium Severity

terminalEvents always calls attachTerminal with hardcoded columns: 80 and rows: 24. Server attach resizes a running PTY when those fields are present, so opening the view or switching sessions can shrink the shell away from the Ghostty-measured grid until a later resize catches up, and newly created attach sessions start at the wrong size.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ff13ebd. Configure here.


private func authenticationDidFinish() {
Task { _ = await onAuthenticationChanged() }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auth cover lingers after email sign-in

Medium Severity

After email sign-in, AuthView only dismisses the inner sheet. authenticationDidFinish then runs onAuthenticationChanged in a detached Task that awaits a full controller.refresh(), including every environment status fetch, before clearing isAuthPresented. Users briefly land back on the custom provider screen while already signed in, and the same full refresh also keeps the OAuth button spinner up after a successful social sign-in.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 32318ec. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 21 total unresolved issues (including 20 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b25ca74. Configure here.

}
.frame(minWidth: proxy.size.width, alignment: .leading)
.padding(.vertical, 10)
.padding(.trailing, 14)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source view always scrolls horizontally

Medium Severity

FeatureSourceTextView sets the stack minWidth to the viewport width, then adds trailing padding outside that frame. Scrollable content is therefore always wider than the viewport, so even short files get horizontal overflow. That fights vertical scrolling in the bidirectional ScrollView. The review diff path avoids this by keeping horizontal padding inside each row’s intrinsic width before applying minWidth.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b25ca74. Configure here.

@lawrence-millard

Copy link
Copy Markdown

hey jarvis merge this

.toolbarBackground(.hidden, for: .navigationBar)
}
.task {
if clerk.environment == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Connection/T3ConnectView.swift:347

refreshEnvironment() errors are discarded with try?, so if the environment request fails, clerk.environment stays nil and providerButtons shows "Loading sign-in options…" forever. OAuth providers never become usable and no error or retry is surfaced. Consider storing the error in errorMessage so the existing alert can display it, and offering a retry path.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/swift-ios/Features/Connection/T3ConnectView.swift around line 347:

`refreshEnvironment()` errors are discarded with `try?`, so if the environment request fails, `clerk.environment` stays `nil` and `providerButtons` shows "Loading sign-in options…" forever. OAuth providers never become usable and no error or retry is surfaced. Consider storing the error in `errorMessage` so the existing alert can display it, and offering a retry path.

<dict>
<key>destination</key>
<string>upload</string>
<key>manageAppVersionAndBuildNumber</key>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium workflows/mobile-eas-production.yml:179

The archive export sets manageAppVersionAndBuildNumber to false while the build number is hard-coded to 12 throughout the workflow. If the upload succeeds but a later step (TestFlight group assignment or beta review submission) fails and the job is rerun, xcodebuild -exportArchive re-uploads the same CFBundleVersion 12, which App Store Connect rejects as a duplicate — the job fails before reaching the idempotent post-upload logic that could have resumed safely. Consider either incrementing CURRENT_PROJECT_VERSION per run or skipping the upload step when build 12 already exists in App Store Connect, so reruns resume at the assignment/review steps instead of re-attempting the upload.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/mobile-eas-production.yml around line 179:

The archive export sets `manageAppVersionAndBuildNumber` to `false` while the build number is hard-coded to 12 throughout the workflow. If the upload succeeds but a later step (TestFlight group assignment or beta review submission) fails and the job is rerun, `xcodebuild -exportArchive` re-uploads the same `CFBundleVersion` 12, which App Store Connect rejects as a duplicate — the job fails before reaching the idempotent post-upload logic that could have resumed safely. Consider either incrementing `CURRENT_PROJECT_VERSION` per run or skipping the upload step when build 12 already exists in App Store Connect, so reruns resume at the assignment/review steps instead of re-attempting the upload.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Requested improvement or new capability. size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants