Uh oh!
There was an error while loading. Please reload this page.
feat(ios): add experimental SwiftUI client - #5178
Conversation
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Warning Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice. Comment |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds a full SwiftUI iOS client with new transport, persistence, terminal, sharing, widget, notification, and T3 Connect authentication capabilities. Its broad runtime surface and changed product defaults, including access and notification behavior, require human review. Not approved because:
Review your spending limits in Billing settings, or comment |
t3dotgg
commented
Aug 1, 2026
t3dotgg
commented
Aug 1, 2026
Mobile interaction polishThis 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.
Verification:
Commits: |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| name: resolvedProviderName, | ||
| isAvailable: false, | ||
| models: [savedModel] | ||
| )) |
There was a problem hiding this comment.
Stub models block image attachments
Medium Severity
ThreadComposerProviderCatalog injects a saved model as FeatureModel(id:name:) with default supportsImages of false. DailyUXModelOptions.supportsImages then treats that stub as authoritative whenever any discovered sibling model advertises images, so the thread composer disables attachments and blocks send for custom or temporarily undiscovered models. Previously a missing catalog entry fell through to the permissive unknown-model path.
Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.
…-app-swift # Conflicts: # docs/user/thread-sidebar.md
| let jti: String | ||
| let iat: Int | ||
| let ath: String? | ||
| } |
There was a problem hiding this comment.
DPoP proofs encode null ath
High Severity
Token-exchange DPoP proofs leave ath nil, and the synthesized Encodable payload writes "ath": null. The shared verifier treats ath as an omitted string key, so those proofs fail as malformed. Relay and environment token exchange both mint proofs without an access token, so T3 Connect cannot complete authorization against the real server.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 1a59936. Configure here.
Add config refresh, server settings, DPoP messages, file previews, and preuploads. Improve Markdown worklogs, model menus, sharing, and native voice input.
…-app-swift # Conflicts: # docs/user/thread-sidebar.md
| finishRefresh(environmentID: environment.id, id: refreshID) | ||
| throw error | ||
| } | ||
| } |
There was a problem hiding this comment.
Sign-out loses in-flight token refresh
High Severity
refreshCredential only registers credential(forEnvironmentID:) as an in-flight authorization operation. After that returns, descriptor fetch and DPoP token exchange keep running outside waitForAuthorizationOperations(). Sign-out can finish, revoke managed state, then the refresh completes and a later persist can reinstall a usable managed credential.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.
| } | ||
| scheduleOutboxDrain() | ||
| return succeeded | ||
| } |
There was a problem hiding this comment.
Onboarding succeeds while environment is down
Medium Severity
pair fails when the installed snapshot is disconnected, but setEnvironmentEnabled and T3 Connect handleConnect do not. Enabling a saved environment or completing a managed connect still reports success after a failed shell load, so onboarding can advance for an unreachable computer.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.
Resume thread and shell subscriptions from applied event cursors. Keep catch-up state separate from connection and turn status, and use a bounded snapshot fallback when live updates stall.
Carries the experimental native SwiftUI iOS client from upstream PR pingdotgg#5178 (branch t3code/rebuild-mobile-app-swift, head 9a3aef3) as a fork feature. The PR has never merged to upstream main. Carried verbatim: apps/swift-ios/** and docs/user/swiftui-mobile.md. Dropped: the PR's .github/workflows/swift-ios.yml, AGENTS.md and .agents/skills edits, edits to existing docs/user pages, and scripts/generate-swift-wire-fixtures.ts (the generated fixtures are committed under apps/swift-ios/Tests/Fixtures/Wire). The PR touches no packages/** or apps/server paths relative to upstream main. Fork-Feature: swift-ios Upstream: pr:5178 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the swift-ios placeholder in fork/FEATURES.md with the live entry: carry source (PR pingdotgg#5178 head 9a3aef3), the paths dropped from the PR, the refresh procedure for carried PRs, local identity override instructions, the build and test commands, and the protocol check against packages/contracts. scripts/fork/seams.ts now treats apps/swift-ios/** and docs/user/swiftui-mobile.md as fork-owned, since a carried PR is additive and not a seam; the regenerated SEAMS.md stays at 29 files. Fork-Feature: swift-ios Upstream: no Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
| detail.page = cached.page | ||
| markThreadCacheRecentlyUsed(route.uiID) | ||
| startDetailStream(route) | ||
| return detail |
There was a problem hiding this comment.
Retry skips a fresh thread snapshot
Medium Severity
loadThread always calls retainActiveThread before the warm-resume gate, so Retry on an already-open thread writes a fresh threadResumeStates entry and then takes that path. The failed snapshot is not refetched until ensureDetailCatchUpFallback waits another catchUpDelay.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.
…-app-swift # Conflicts: # AGENTS.md # docs/user/permission-modes.md
| RPCMethod.serverRefreshProviders.rawValue, | ||
| payload: .object([ | ||
| "refreshModels": .bool(refreshModels), | ||
| ].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }), |
There was a problem hiding this comment.
Workspace refresh omits provider instance
High Severity
refreshProviders sends cwd without instanceId. The server only loads a workspace snapshot when both are present, so this call runs a full provider refresh and never stores skills or slash commands for the current workspace. Thread and new-task composers therefore keep the machine catalog, or an empty one after another workspace snapshot exists.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e706cdc. Configure here.
| errorMessage = "Provider setup failed. Check the connection and try again." | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Provider setup UI stays stale
Medium Severity
Sign-in, sign-out, install, and remove only store the returned event. The account and runtime controls still read provider.authStatus and provider.isInstalled, which stay stale until a manual catalog refresh. A finished sign-in can show Sign in again, and Sign in can stay disabled after a successful install.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.
| throw FeatureCapabilityUnavailable("Native file previews") | ||
| } | ||
| let resolvedURL = if previewKind == .image || previewKind == .video || previewKind == .pdf | ||
| || ["html", "htm"].contains(URL(fileURLWithPath: entry.path).pathExtension.lowercased()) { |
There was a problem hiding this comment.
HTML file preview never runs
Low Severity
The new html/htm media-preview check sits inside the image, PDF, video, and document branch. FeatureFilePreviewKind.infer classifies those extensions as .source, so that branch never runs and HTML files still load as text instead of a media preview.
Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
There are 50 total unresolved issues (including 47 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.
| latestServerConfig = config | ||
| } | ||
| if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) } | ||
| return settings |
There was a problem hiding this comment.
Preference saves can wipe catalogs
High Severity
updateServerPreferences fetches a full serverConfig for the capability check, then saveServerPreferences ignores it and rebuilds the cached snapshot from serverConfigsByEnvironmentID. A missing cache entry stores empty providers and publishes that snapshot, so changing an icon or shared preference can clear the model catalog until another refresh.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.
| guard let expected = serverConfigsByEnvironmentID[environmentID]?.settings?.sharedPatch else { return [] } | ||
| return sharedPreferenceTargetIDs.filter { id in | ||
| id != environmentID && serverConfigsByEnvironmentID[id]?.settings?.sharedPatch != expected | ||
| }.map { id in latestSnapshot?.environments.first { $0.id == id }?.name ?? id } |
There was a problem hiding this comment.
Mismatches miss uncached settings
Medium Severity
serverPreferences returns settings from the network without storing them, while sharedPreferenceMismatches only reads serverConfigsByEnvironmentID. After a cache miss the Preferences screen shows the loaded values but an empty difference list, so “Use this environment’s preferences” never appears until something else writes the cache.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.
| private func makeUploadAttachments( | ||
| _ attachments: [FeatureUploadAttachment] | ||
| ) throws -> [UploadChatAttachment] { | ||
| gu |
There was a problem hiding this comment.
Work-log icons drop later
Low Severity
Each work-log activity assigns toolPresentation from the current payload or that tool’s stored presentation, and a miss writes nil. A later generic tool therefore erases an earlier browser or computer icon, so the grouped work-log header loses its mark after ordinary file or command updates.
Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.






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 IDcom.t3tools.t3code.swiftui.Try it
apps/swift-ios/T3Code.xcodeprojin Xcode.T3Codescheme and an iOS 17+ simulator or device.See
apps/swift-ios/README.mdfor architecture, included functionality, and known gaps.What to test
Preview
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 MERGEonly after explicit maintainer approvalThis PR was built by GPT-5.6-sol using the Codex harness in T3 Code.
Note
High Risk
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with
packages/contractsand server behavior.Overview
Introduces a standalone native SwiftUI iOS app under
apps/swift-iosthat talks to T3 servers directly (separate from the React Native app inapps/mobile), with its own Xcode project, dev identity (T3 Swift Dev/t3code-swiftui-dev), and side-by-side install.Adds T3 Connect cloud plumbing in Swift: Clerk session handling, device-bound DPoP signing, relay HTTP client, managed-environment token exchange, and runtime authorization wired into the native connection stack.
Contributor and agent docs now treat mobile as two clients—
AGENTS.md,test-t3-mobile,test-t3-app, andios-debugger-agentspell out when to build Metro/React Native vs native SwiftUI and warn against cross-client verification.Adds CI via
.github/workflows/swift-ios.yml: checks generated Swift wire fixtures (generate-swift-wire-fixtures.ts --check) and runsapps/swift-ios/Scripts/ci-test.shon macOS whenapps/swift-iosor contracts change.Reviewed by Cursor Bugbot for commit 459cc4e. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add experimental native SwiftUI iOS client with widgets, share extension, and T3 Connect
apps/swift-ios/with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and aNativeFeatureClientcomposing all feature surfacesswift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scriptsT3Code.xcodeprojembeds Widget and Share extensions whose app-group identifier depends on theT3CODE_APP_GROUP_IDENTIFIERbuild setting being correctly configured per environmentMacroscope summarized 459cc4e.