Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
12b34e8
fix(mobile): Forgejo change requests ask for the summary they require…
flamboh Oct 12, 2026
b9ad3da
fix(server): cap automatic thread continuations and turn them on by d…
t3dotgg Oct 12, 2026
c32ade4
feat(mobile): stock tab bar, glass filter pills, and saved list filte…
juliusmarminge Oct 12, 2026
52f9046
fix(mobile): keep PR surfaces and Android tab selection visible acros…
PixPMusic Oct 12, 2026
4ab4cdf
fix(mobile): keep preview count badges inside the working pill (#18449)
RVZO6 Oct 12, 2026
2e24ec4
fix(mobile): show rounded pull request selection in the sidebar (#18480)
PixPMusic Oct 12, 2026
e688034
feat(web): model options for source control writer and review guide m…
juliusmarminge Oct 12, 2026
19bef8c
ci(mobile): cut a store build when the native fingerprint has no buil…
juliusmarminge Oct 12, 2026
37e2acd
fix(web): terminal colors no longer look washed out (#17168)
psv2522 Oct 12, 2026
d514a8c
refactor(settings): centralize schema-declared secrets and inputs (#1…
juliusmarminge Oct 12, 2026
6f6197c
fix(mobile): restore native scroll-edge effects on forms and PRs (#18…
juliusmarminge Oct 12, 2026
462e7f4
feat(mobile): native-sized pull request action bar (#18527)
juliusmarminge Oct 12, 2026
679b03e
fix(mobile): preserve theme contrast across surfaces and actions (#18…
juliusmarminge Oct 12, 2026
a39fd7b
refactor(mobile): share themed list rows and sections (#18510)
juliusmarminge Oct 12, 2026
9a2b9a2
refactor(mobile): share themed toggle rows in settings (#18511)
juliusmarminge Oct 12, 2026
abc80c8
refactor(mobile): share themed choice rows in settings (#18516)
juliusmarminge Oct 12, 2026
48595c7
fix(server): consolidate browser credential handling (#18509)
juliusmarminge Oct 12, 2026
5fe7568
feat: open T3 Code from a link with a prompt ready to send (#18454)
t3dotgg Oct 12, 2026
8f25cf9
fix(release): drop placeholder allowBuilds entry that broke nightly b…
t3dotgg Oct 12, 2026
eac6ba5
ci: fail PRs on invalid allowBuilds entries in pnpm-workspace.yaml (#…
t3dotgg Oct 12, 2026
f498b35
fix(mobile): menu dismiss taps no longer press the view underneath (#…
juliusmarminge Oct 12, 2026
d83929a
feat(web): one sidebar click updates every machine and provider (#18053)
t3dotgg Oct 12, 2026
5d59e71
fix(release): avoid PowerShell crashes on Windows ARM (#18573)
t3dotgg Oct 12, 2026
b3871d5
fix(server): Forgejo pull requests report their head commit (#18463)
flamboh Oct 12, 2026
a27cf8a
test(provider-acp): terminal kill test passes on Windows hosts (#18450)
AdEx-Partners-DE Oct 12, 2026
58d2532
fix(web): context meter no longer reads 1000k just under a million to…
luke2x Oct 12, 2026
5753207
fix(desktop): avoid Hyprland 0.48–0.50 Wayland color-manager startup …
krankos Oct 12, 2026
7b6d907
fix(mobile): iPhones on iOS 27.2 beta no longer open to a black scree…
shivamhwp Oct 12, 2026
701fc83
fix(mobile): installs skip the Enriched math download again (#18679)
juliusmarminge Oct 12, 2026
9e55228
fix(web): setup wizards follow custom themes (#18725)
SunkenInTime Oct 12, 2026
b821fa0
fix(server): resolve configured PR bases to branch names (#18754)
Yash-Singh1 Oct 12, 2026
0e7346c
fix: expose running terminals and prevent setup install stalls (#18740)
Yash-Singh1 Oct 12, 2026
511d20c
fix(mobile): hide Claude Read image placeholders when previews exist …
Yash-Singh1 Oct 12, 2026
a3c4e9e
fix(gitlab): merge request details report their head commit (#18608)
ScottN-PV Oct 12, 2026
ffb4af8
feat(server): rename built-in models from settings (#18840)
t3dotgg Oct 12, 2026
b03534f
fix(web): Git actions follow the selected thread (#18862)
Yash-Singh1 Oct 13, 2026
979589d
fix(desktop): preserve updater failure causes in diagnostics (#18864)
Yash-Singh1 Oct 13, 2026
34f8458
fix(web): attach dropped threads to pending answers (#18789)
Yash-Singh1 Oct 13, 2026
0179552
fix(web): keep button text stable during press scaling (#18866)
Yash-Singh1 Oct 13, 2026
8110edf
fix(bitbucket): preserve PR head commits for review guides (#18402)
ahilles107 Oct 13, 2026
e2e95a3
fix(server): return null for unresolved optional commands (#18871)
Yash-Singh1 Oct 13, 2026
5d6e419
fix: show computer-use action titles and align app icons (#18865)
Yash-Singh1 Oct 13, 2026
265fb70
fix(server): apply confirmed Claude subagent outcomes (#18778)
Yash-Singh1 Oct 13, 2026
41d86c1
fix(mobile): Browser page no longer logs a duplicate key warning (#18…
tris203 Oct 13, 2026
df26ef9
fix(web): a numeric q in the pull requests URL no longer crashes the …
ScottN-PV Oct 13, 2026
a143fa3
fix(client): T3 Connect clients reconnect once a new tunnel is ready …
shivamhwp Oct 13, 2026
346247b
fix(web): leaving the home page while the app loads no longer bounces…
shivamhwp Oct 13, 2026
1af1386
fix(mobile): attachment images no longer re-download on every reconne…
shivamhwp Oct 13, 2026
e24ea1d
fix(server): a migration id clash stops startup with a clear error in…
shivamhwp Oct 13, 2026
5a65169
fix(server): a thread with an event type from a newer build can still…
shivamhwp Oct 13, 2026
dc19285
fix(server): Codex titles and branch names no longer start the user's…
brycedbjork Oct 13, 2026
06620fa
fix(server): Claude thinking before a tool call stays a collapsed Tho…
maria-rcks Oct 13, 2026
ae7bed6
perf(server): cached branch PR lookups no longer start git processes …
SunkenInTime Oct 13, 2026
80664d5
fix(web): compact merge buttons show when they need a second click (#…
TonybynMp4 Oct 13, 2026
5070a83
fix(mobile): pull requests load from another environment when theirs …
juliusmarminge Oct 13, 2026
540a3e8
perf(web): collect active tool calls without array shifts (#18853)
saphid Oct 13, 2026
e9221e4
fix(web): center the pace icon on the limit card's "left" label (#18580)
akash3444 Oct 13, 2026
6abccc1
fix(web): update everything includes npm-installed servers again (#18…
ScottN-PV Oct 13, 2026
bbd98a5
fix(server): review guides find Bitbucket heads reported by short has…
ahilles107 Oct 13, 2026
e82a1ce
fix(web): scheduled task model picker no longer shifts the dialog (#1…
flamboh Oct 13, 2026
6524a8d
fix(ci): CodeRabbit reviews no longer request changes (#18954)
Yash-Singh1 Oct 13, 2026
98a6246
fix(web): links in HTML renders open like chat links (#18897)
maria-rcks Oct 13, 2026
a47decf
fix(web): remember editor selections before opening folders (#19078)
Yash-Singh1 Oct 13, 2026
42160f2
fix(ci): retry Cursor webhook HTTP 400s on main (#19077)
Yash-Singh1 Oct 13, 2026
8d75232
fix(web): let context handoff details use the available width (#19098)
Yash-Singh1 Oct 13, 2026
05e4f88
fix(server): snoozed threads allow queued automatic wakes (#19099)
Yash-Singh1 Oct 13, 2026
6a68bee
fix(web): merge button fits its label and animates width changes (#18…
flamboh Oct 13, 2026
446be69
fix(web): Cmd+Enter creates the new project while an option is highli…
SunkenInTime Oct 13, 2026
9a9fd2e
fix(pi): queue turn-start prompts when native work is running (#18639)
StiensWout Oct 13, 2026
4d76835
fix(pi): load T3 tool schemas for completion gateways (#18643)
StiensWout Oct 13, 2026
4292720
fix(devices): report agent tool startup failures (#18502)
PixPMusic Oct 13, 2026
8ac4b97
fix(web): settings warn when an environment silently drops a setting …
flamboh Oct 13, 2026
380bba3
fix(desktop): stop unsolicited Messages code autofill (#19118)
StiensWout Oct 13, 2026
a76d689
feat(providers): add Factory Droid as a native provider
jhomra21 Oct 12, 2026
921438f
test(droid): add adapter tests, replay harness, recorder and recorded…
jhomra21 Oct 12, 2026
bc74436
docs(droid): document the Factory Droid provider
jhomra21 Oct 12, 2026
01b47e5
fix(droid): report fresh context usage after compaction
jhomra21 Oct 13, 2026
7bd1287
fix(droid): isolate status model discovery
jhomra21 Oct 13, 2026
0ab69ae
test(droid): add provider_thread_resume replay
jhomra21 Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 2 additions & 3 deletions .coderabbit.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { defineConfig, type CodeRabbitContext } from "@coderabbitai/config";

// Org members and collaborators merge their own pull requests. On anyone else's, CodeRabbit
// requests changes until its comments are resolved, then approves.
// Org members and collaborators can approve their own pull requests.
const UNGATED_AUTHORS = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
const isGated = ({ pr }: CodeRabbitContext) => !UNGATED_AUTHORS.has(pr?.authorAssociation ?? "");

export default defineConfig((ctx) => ({
reviews: {
high_level_summary: false,
review_status: false,
request_changes_workflow: isGated(ctx),
request_changes_workflow: false,
allow_author_approval: !isGated(ctx),
auto_review: {
enabled: true,
Expand Down
26 changes: 20 additions & 6 deletions .github/workflows/cursor-hygiene-webhook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,23 @@ jobs:
echo "Missing CURSOR_T3CODE_WEBHOOK_URL or CURSOR_T3CODE_WEBHOOK_AUTH — skipping."
exit 0
fi
curl -fsS --max-time 60 -X POST "$URL" \
-H "Authorization: $AUTH" \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: ${{ github.event_name }}" \
-H "X-GitHub-Delivery: ${{ github.run_id }}-${{ github.run_attempt }}" \
--data-binary @"${{ github.event_path }}"
for attempt in 1 2 3; do
curl_status=0
http_status=$(curl -fsS --max-time 60 -X POST "$URL" \
--output /dev/null --write-out '%{http_code}' \
-H "Authorization: $AUTH" \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: ${{ github.event_name }}" \
-H "X-GitHub-Delivery: ${{ github.run_id }}-${{ github.run_attempt }}" \
--data-binary @"${{ github.event_path }}") || curl_status=$?
if [ "$curl_status" -eq 0 ]; then
exit 0
fi
if [ "$GITHUB_REF" != "refs/heads/main" ] || \
[ "$curl_status" -ne 22 ] || [ "$http_status" != "400" ] || \
[ "$attempt" -eq 3 ]; then
exit "$curl_status"
fi
echo "Cursor webhook returned HTTP 400; retrying in 5 seconds ($attempt/2)."
sleep 5
done
109 changes: 62 additions & 47 deletions .github/workflows/mobile-eas-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ name: Mobile EAS Production
# runner, with corepack pinning pnpm 10.24 in eas.json, local == build.
#
# Every merge to main that touches the mobile app reconciles, per platform:
# 1. Store builds: if the latest production build's version differs from
# app.config.ts, cut a new build and submit it (TestFlight + Play internal
# track). Bumping `version` is therefore all it takes to
# start the next release train — the first build of a version enters
# 1. Store builds: when no production build matches the current native
# fingerprint, or the latest build's version differs from app.config.ts,
# cut a new build and submit it (TestFlight + Play internal track). Native
# changes therefore ship a binary on merge, and bumping `version` is all
# it takes to start the next release train — the first build of a version enters
# external-TestFlight beta review immediately, and later builds of the
# same version auto-approve until that version is released. After App
# Store approval, Apple closes the release train and `version` must be
Expand All @@ -21,10 +22,11 @@ name: Mobile EAS Production
# fingerprint. Older binaries of the same major version with a matching
# fingerprint receive it too; the major version is part of the
# fingerprint (apps/mobile/fingerprint.config.js), so a new major never
# reaches the previous major's store binaries over the air. When native
# drift means no binary could install the update,
# it is skipped and flagged in the job summary instead of published
# into the void.
# reaches the previous major's store binaries over the air. Until a
# build for the new fingerprint finishes, the OTA is skipped rather than
# published into the void.
# Hold native-change PRs (the "📱 Native Change" label) and merge them as a
# batch to avoid a store build per PR.
# workflow_dispatch remains as a manual override for both modes (e.g. to
# retry an errored build or force an OTA).
# Manual v2-preview builds keep the production app identity and disable OTA.
Expand Down Expand Up @@ -263,61 +265,74 @@ jobs:
--message "${{ inputs.message || format('Production OTA ({0})', github.sha) }}" \
--non-interactive

# No --status filter on build:list: an in-queue/in-progress build must
# count as existing, or every merge during the build window would cut a
# duplicate. After an errored build, retry via workflow_dispatch
# mode=build — pushes won't re-trigger it until the app version changes.
- id: store_builds
name: Ensure store builds exist for the current app version
# One pass per platform. The fingerprint decides OTA vs store build:
# - a finished build matches → publish the OTA;
# - a queued/in-progress build matches → wait, the next merge after it
# finishes publishes the OTA (no duplicate build per merge);
# - no build matches → native drift, so cut and submit a new build.
# A version bump also cuts a build even when native code is unchanged,
# because only the major version is part of the fingerprint.
# An errored build for this fingerprint is not retried on push (it would
# rebuild on every merge); retry via workflow_dispatch mode=build.
- id: reconcile
name: Publish OTA or cut store build
if: steps.expo-token.outputs.present == 'true' && github.event_name == 'push'
continue-on-error: true
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
run: |
failed=0
version="$(npx expo config --json --type public | jq -r '.version')"
for platform in ios android; do
latest="$(eas build:list --platform "$platform" --build-profile production --limit 1 --json --non-interactive | jq -r '.[0].appVersion // "none"')"
if [ "$latest" = "$version" ]; then
echo "$platform: production build for $version already exists (or is in progress)"
continue
fi
echo "$platform: latest production build is $latest, app.config.ts says $version — building"
if eas build --platform "$platform" --profile production --auto-submit --non-interactive --no-wait; then
echo ":building_construction: $platform: scheduled production build and submission for $version" >> "$GITHUB_STEP_SUMMARY"
else
failed=1
echo ":x: $platform: production build or submission failed for $version" >> "$GITHUB_STEP_SUMMARY"
fi
done
exit "$failed"

- name: Publish fingerprint-gated OTA
if: steps.expo-token.outputs.present == 'true' && github.event_name == 'push'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
run: |
message="$(git log -1 --pretty=%s | head -c 120) ($(git rev-parse --short=9 HEAD))"
for platform in ios android; do
# eas-cli prints an environment-loaded notice to stdout before the
# JSON even with --json, so discard everything before the document.
hash="$(eas fingerprint:generate --platform "$platform" --environment production --json --non-interactive | sed -n '/^{/,$p' | jq -er '.hash | select(type == "string" and length > 0)')"
matching="$(eas build:list --platform "$platform" --build-profile production --status finished --fingerprint-hash "$hash" --limit 1 --json --non-interactive | jq 'length')"
if [ "$matching" -gt 0 ]; then
eas update \
matches="$(eas build:list --platform "$platform" --build-profile production --fingerprint-hash "$hash" --limit 20 --json --non-interactive)"
finished="$(jq '[.[] | select(.status == "FINISHED")] | length' <<<"$matches")"
active="$(jq '[.[] | select(.status == "NEW" or .status == "IN_QUEUE" or .status == "IN_PROGRESS")] | length' <<<"$matches")"
errored="$(jq '[.[] | select(.status == "ERRORED")] | length' <<<"$matches")"
latest_version="$(eas build:list --platform "$platform" --build-profile production --limit 1 --json --non-interactive | jq -r '.[0].appVersion // "none"')"

if [ "$finished" -gt 0 ]; then
if eas update \
--channel production \
--environment production \
--platform "$platform" \
--message "$message" \
--non-interactive
echo ":white_check_mark: $platform: OTA published to production (fingerprint \`$hash\`)" >> "$GITHUB_STEP_SUMMARY"
--non-interactive; then
echo ":white_check_mark: $platform: OTA published to production (fingerprint \`$hash\`)" >> "$GITHUB_STEP_SUMMARY"
else
failed=1
echo ":x: $platform: OTA publish failed (fingerprint \`$hash\`)" >> "$GITHUB_STEP_SUMMARY"
fi
fi

reason=""
if [ "$finished" -eq 0 ] && [ "$active" -eq 0 ]; then
if [ "$errored" -gt 0 ]; then
echo ":x: $platform: the production build for fingerprint \`$hash\` errored — retry with workflow_dispatch mode=build" >> "$GITHUB_STEP_SUMMARY"
failed=1
continue
fi
reason="native fingerprint \`$hash\` has no build"
elif [ "$latest_version" != "$version" ] && [ "$active" -eq 0 ]; then
reason="latest production build is $latest_version, app.config.ts says $version"
fi

if [ -z "$reason" ]; then
if [ "$finished" -eq 0 ]; then
echo ":hourglass: $platform: production build for fingerprint \`$hash\` is in progress — OTA resumes on the next merge after it finishes" >> "$GITHUB_STEP_SUMMARY"
fi
continue
fi

echo "$platform: $reason — building"
if eas build --platform "$platform" --profile production --auto-submit --non-interactive --no-wait; then
echo ":building_construction: $platform: scheduled production build and submission for $version ($reason)" >> "$GITHUB_STEP_SUMMARY"
else
echo ":warning: $platform: no finished production build matches fingerprint \`$hash\` — OTA skipped; JS changes reach $platform only once a matching build ships" >> "$GITHUB_STEP_SUMMARY"
failed=1
echo ":x: $platform: production build or submission failed for $version" >> "$GITHUB_STEP_SUMMARY"
fi
done

- name: Propagate store build failure
if: steps.store_builds.outcome == 'failure'
run: exit 1
exit "$failed"
33 changes: 33 additions & 0 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,39 @@ jobs:
git sparse-checkout set --no-cone '/*' '!/.repos/'
git -c checkout.workers=0 checkout --detach FETCH_HEAD

# The runner's PowerShell 7.6/.NET 10 intermittently crashes during signing:
# https://github.com/dotnet/runtime/issues/131636
# Put the .NET 8 release first for both workflow shells and electron-builder.
- name: Use PowerShell 7.4 for Windows ARM builds
if: inputs.platform == 'win' && inputs.arch == 'arm64'
shell: powershell
run: |
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
$version = "7.4.20"
$archive = Join-Path $env:RUNNER_TEMP "PowerShell-$version-win-arm64.zip"
$installDir = Join-Path $env:RUNNER_TEMP "powershell-$version-arm64"
Invoke-WebRequest -UseBasicParsing `
-Uri "https://github.com/PowerShell/PowerShell/releases/download/v$version/PowerShell-$version-win-arm64.zip" `
-OutFile $archive
$expectedHash = "67eff749d5dae357501c95a3eec1dcd93f669824e3ca2b1d2adcdecdebf04f35"
if ((Get-FileHash $archive -Algorithm SHA256).Hash -ne $expectedHash) {
throw "PowerShell archive checksum mismatch"
}
Expand-Archive -Path $archive -DestinationPath $installDir
$installDir | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
& "$installDir\pwsh.exe" -NoProfile -NonInteractive -Command '$PSVersionTable; [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Verify Windows ARM PowerShell selection
if: inputs.platform == 'win' && inputs.arch == 'arm64'
shell: pwsh
run: |
$expectedPath = Join-Path $env:RUNNER_TEMP "powershell-7.4.20-arm64\pwsh.exe"
if ((Get-Command pwsh.exe).Source -ne $expectedPath -or $PSVersionTable.PSVersion -ne [version]"7.4.20") {
throw "Windows ARM builds must use the pinned PowerShell runtime"
}

# Windows installs straight from the registry. There, linking the packages
# takes the time, not downloading them, so a ~700 MB package cache did not
# pay for its own restore. Across recent nightlies, restore plus install
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

T3 Code is an "agent harness control surface". It enables control of the agents on your machine with a best-in-class mobile app ([iOS](https://apps.apple.com/us/app/t3-code-remote-claude-more/id6787819824), [Android](https://play.google.com/store/apps/details?id=com.t3tools.t3code)), [web app](https://app.t3.codes) and [Electron-based desktop app](https://t3.codes).

Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, and Google Antigravity. If they're set up on your computer, T3 Code can control them.
Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, OpenCode, Google Antigravity, and Factory Droid. If they're set up on your computer, T3 Code can control them.

## "Wait, what are you selling me?"

Expand All @@ -13,14 +13,15 @@ We wanted something performant, remote-ready, and truly open. If we ever go the
## Installation

> [!WARNING]
> T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, and Antigravity. Install and authenticate at least one provider before use:
> T3 Code currently supports Codex, Claude, Cursor, Grok Build, OpenCode, Antigravity, and Factory Droid. Install and authenticate at least one provider before use:
>
> - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login`
> - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login`
> - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `agent login`
> - Grok Build: install [Grok Build CLI](https://x.ai/cli) and run `grok login`
> - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login`
> - Antigravity: enable it in Settings, then use **Install Antigravity** and **Sign in with Google**. No CLI is required.
> - Factory Droid: install with `curl -fsSL https://app.factory.ai/cli | sh`, then sign in on the server.

### Command line

Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const APP_BUNDLE_ID = isDevelopment
? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
: "com.t3tools.t3code";
const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
const LAUNCHER_VERSION = 19;
const LAUNCHER_VERSION = 20;
const developmentMacIconPngPath = NodePath.join(
repoRoot,
"assets",
Expand Down Expand Up @@ -280,6 +280,8 @@ function patchMainBundleInfoPlist(appBundlePath, iconPath, executableName) {
for (const [key, value] of Object.entries(resolveMacBundleInfoPlistStrings(executableName))) {
setPlistString(infoPlistPath, key, value);
}
// macOS 26 offers Messages/Mail codes in every text field unless opted out.
setPlistJson(infoPlistPath, "NSAutoFillRequiresTextContentTypeForOneTimeCodeOnMac", true);
setPlistJson(infoPlistPath, "CFBundleURLTypes", [
{
CFBundleURLName: APP_BUNDLE_ID,
Expand Down
13 changes: 11 additions & 2 deletions apps/desktop/src/app/DesktopClerk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ it.effect.each(["startup", "open-url"] as const)(
}).pipe(Effect.scoped),
);

it.effect("hands a web link to the renderer and leaves other links alone", () =>
it.effect("hands web and app links to the renderer and leaves other links alone", () =>
Effect.gen(function* () {
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn(), isPrimaryInstance: true });
Expand All @@ -420,10 +420,19 @@ it.effect("hands a web link to the renderer and leaves other links alone", () =>
// macOS hands the default browser every web link.
assert.strictEqual(open("https://example.com/page"), 1);
assert.strictEqual(open("http://localhost:3000/"), 1);
// Prompt and thread links ride the same queue; the renderer routes them in the app.
assert.strictEqual(open("t3code://new?prompt=hi"), 1);
assert.strictEqual(open("t3code://threads/env-1/thread-1"), 1);
// Anything else is not a web page; Electron keeps its own handling.
assert.strictEqual(open("mailto:hello@example.com"), 0);
assert.strictEqual(open("t3code://somewhere-else"), 0);
yield* Effect.yieldNow;
assert.deepStrictEqual(received, ["https://example.com/page", "http://localhost:3000/"]);
assert.deepStrictEqual(received, [
"https://example.com/page",
"http://localhost:3000/",
"t3code://new?prompt=hi",
"t3code://threads/env-1/thread-1",
]);
}).pipe(
Effect.provide(layerDesktopClerk(true, [], "darwin")),
Effect.provideService(HostProcess.Arguments, ["t3"]),
Expand Down
Loading
Loading