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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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

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

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

Conversation

@t3dotgg

@t3dotggt3dotgg 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

HomeThread
SwiftUI home with a large thread collectionSwiftUI 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
Adds a new native client with Clerk auth, DPoP, relay tokens, and managed-environment credentials—security-sensitive paths that must stay aligned with packages/contracts and server behavior.

Overview
Introduces a standalone native SwiftUI iOS app under apps/swift-ios that talks to T3 servers directly (separate from the React Native app in apps/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, and ios-debugger-agent spell 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 runs apps/swift-ios/Scripts/ci-test.sh on macOS when apps/swift-ios or 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

  • Introduces a full native SwiftUI iOS app under apps/swift-ios/ with core transport (WebSocket RPC, HTTP), pairing/T3 Connect DPoP auth, and a NativeFeatureClient composing all feature surfaces
  • Adds SwiftUI screens for threads, composer, workspace, settings, connections, devices, usage, source control, reviews, files, and terminal (backed by GhosttyKit)\n- Ships WidgetKit extensions (recent-tasks widget + Live Activity) and a Share Services extension that stages shared text/images/files into a shared-inbox via the app group container
  • Includes an extensive test suite covering wire contracts, transport reliability, pairing, usage, Markdown, voice input, attachments, and platform features
  • Adds CI workflow (swift-ios.yml), wire-fixture generator (scripts/generate-swift-wire-fixtures.ts), and device install scripts
  • Risk: this is a large new codebase with no backward-compatibility surface; the T3Code.xcodeproj embeds Widget and Share extensions whose app-group identifier depends on the T3CODE_APP_GROUP_IDENTIFIER build setting being correctly configured per environment

Macroscope summarized 459cc4e.

@coderabbitai

coderabbitaiBot 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: Team

Run ID: 355f5cdf-7ba6-4194-ae4f-03b732623002

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

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 @coderabbitai help to get the list of available commands.

@t3dotggt3dotgg 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-actionsgithub-actionsBot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Aug 1, 2026
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift
Comment threadapps/swift-ios/Core/JSONValue.swift
Comment threadapps/swift-ios/Features/Workspace/DailyUXModels.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
@macroscopeapp

macroscopeappBot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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:

  • Per-review cost limit exceeded (workspace setting). Approvability relies on correctness review in order to determine eligibility

Review your spending limits in Billing settings, or comment @macroscope-app review this PR to bypass the limit and review now. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

Copy link
Copy Markdown
MemberAuthor

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.

BeforeAfter
Model picker before, using initial badgesModel 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

Copy link
Copy Markdown
MemberAuthor

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 homeHarness-aware home
Previous SwiftUI homeSwiftUI home with harness icons
Previous threadKeyboard-pinned composer
Previous SwiftUI threadSwiftUI 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 threadapps/swift-ios/Features/Workspace/NewThreadView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Chat/ThreadDetailView.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/WorkspaceView.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/T3Client.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Workspace/NewThreadView.swift
Comment threadapps/swift-ios/Features/Connection/ConnectionOnboardingView.swift Outdated
Comment threadapps/swift-ios/Features/Chat/FeatureComposerRequestViews.swift Outdated
Comment threadapps/swift-ios/Features/Connection/ConnectionDetails.swift Outdated
Comment threadapps/swift-ios/Features/Workspace/ProviderModelPicker.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift
Comment threadapps/swift-ios/Features/Files/FeatureFilesView.swift Outdated
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/Core/WebSocketRPC.swift
Comment threadapps/swift-ios/App/Platform/PlatformNotifications.swift Outdated
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/Resources/Info.plist Outdated
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectRelayClient.swift Outdated
Comment threadapps/swift-ios/Features/Settings/SettingsView.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectConfiguration.swift
Comment threadapps/swift-ios/Scripts/ci-test.sh
Comment threadapps/swift-ios/Core/T3Client.swift Outdated
Comment threadapps/swift-ios/App/NativeFeatureClient.swift
Comment threadapps/swift-ios/App/Cloud/T3ConnectManagedAuthorization.swift
Comment threadapps/swift-ios/App/Platform/PlatformDeepLinks.swift Outdated
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift
Comment threadapps/swift-ios/Extensions/Share/SharePayloadLoader.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformCloudDelivery.swift Outdated
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/Features/Root/FeatureRootModel.swift
Comment threadapps/swift-ios/App/Platform/PlatformAgentAwareness.swift
Comment threadapps/swift-ios/App/NativeFeatureClient.swift Outdated
Comment threadapps/swift-ios/Scripts/resolve-device-udid.swift Outdated
name: resolvedProviderName,
isAvailable: false,
models: [savedModel]
))

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9d3d8d5. Configure here.

…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 1, 2026
let jti: String
let iat: Int
let ath: String?
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 1a59936. Configure here.

@github-actionsgithub-actionsBot added the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 2, 2026
…-app-swift
# Conflicts:
#	docs/user/thread-sidebar.md
@github-actionsgithub-actionsBot removed the 📱 Native Change Changes the native fingerprint; merging blocks production OTAs until a new store build ships. label Sep 3, 2026
finishRefresh(environmentID: environment.id, id: refreshID)
throw error
}
}

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 9a3aef3. Configure here.

}
scheduleOutboxDrain()
return succeeded
}

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.

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)
Fix in CursorFix in Web

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.
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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>
q1 added a commit to q1/q1code that referenced this pull request Sep 3, 2026
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

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7f6e659. Configure here.

Comment threadapps/swift-ios/Core/T3Client.swift Outdated
RPCMethod.serverRefreshProviders.rawValue,
payload: .object([
"refreshModels": .bool(refreshModels),
].merging(cwd.map { ["cwd": .string($0)] } ?? [:]) { _, new in new }),

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit e706cdc. Configure here.

errorMessage = "Provider setup failed. Check the connection and try again."
}
}
}

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.

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)
Fix in CursorFix in Web

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()) {

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 00be9a1. Configure here.

@cursorcursorBot 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 default effort and found 3 potential issues.

There are 50 total unresolved issues (including 47 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 459cc4e. Configure here.

latestServerConfig = config
}
if let shell = shellsByEnvironmentID[environmentID] { await emitSnapshot(shell) }
return settings

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.

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)
Fix in CursorFix in Web

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 }

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.

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)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

private func makeUploadAttachments(
_ attachments: [FeatureUploadAttachment]
) throws -> [UploadChatAttachment] {
gu

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.

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.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 459cc4e. Configure here.

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

Labels

enhancementRequested improvement or new capability.size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@t3dotgg@lawrence-millard@saphid@mackinleysmith