Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4092c4a
fix(client): retry the permissions check after a reconnect (#27008)
juliusmarminge Oct 29, 2026
da04ebc
fix(mobile): tab bar spans the screen on iOS before 26 (#27089)
juliusmarminge Oct 29, 2026
624e70e
perf(server): thread recovery reads are 10x faster (#27099)
juliusmarminge Oct 29, 2026
3898c32
perf(server): source control discovery no longer stalls up to 10s on …
juliusmarminge Oct 29, 2026
7f39a86
perf: huge checkpoints ship up to 75x smaller to clients (#27110)
juliusmarminge Oct 29, 2026
8bf4822
perf(server): sidebar shell loads 3x faster and startup thread counts…
juliusmarminge Oct 29, 2026
22c37a8
perf(server): preparing a run is 15x faster and uses 200x less memory…
juliusmarminge Oct 29, 2026
2f4da5d
feat(server): traces name what grew the heap, for ~0.3µs per span (#2…
juliusmarminge Oct 29, 2026
52f4ec6
perf(gitcafe): PR sync makes 2x fewer GitCafe requests for unchanged …
juliusmarminge Oct 29, 2026
facaa08
perf(shared): SQLite reads stop queueing behind each other, up to 5x …
juliusmarminge Oct 29, 2026
7c29f2d
perf(server): git status runs in parallel and repo detection spawns 3…
juliusmarminge Oct 29, 2026
65b0ab8
fix(client): dead connection routes are probed 30x less often (#27101)
juliusmarminge Oct 29, 2026
dd14848
perf(server): idle servers run 100x fewer background queries and 4.8M…
juliusmarminge Oct 29, 2026
812c0a7
feat(devices): support Android EAS Simulator sessions (#26797)
SchroederNathan Oct 29, 2026
cba3059
fix(web,mobile): image-only messages show the image, not the placehol…
maria-rcks Oct 30, 2026
b42adff
ci(mobile): production deploys use a Release Manager Expo token (#27285)
juliusmarminge Oct 30, 2026
f58fad0
ci(mobile): maintainers can deploy EAS previews for reviewed fork com…
juliusmarminge Oct 30, 2026
d7b6cf7
fix(server): agents yield CPU, tasks, and reclaim to the server under…
juliusmarminge Oct 30, 2026
44d508f
feat(server): `t3 service protect` keeps the server alive when agents…
juliusmarminge Oct 30, 2026
8ab098a
fix(server): held queues release agent and scheduled messages once th…
juliusmarminge Oct 30, 2026
36dac68
fix(server): restart continuations retry failed starts and respect a …
juliusmarminge Oct 30, 2026
89309af
Merge tag 'v0.1.6-nightly.20261030.3904' into custom-nightly
chamaquita-d3[bot] Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions .github/workflows/mobile-eas-preview-fork.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Mobile EAS Preview (Fork)

# EAS previews for fork PRs. This runs the PR's code with an Expo token: the
# app config and plugins run on this runner, and native builds run on EAS
# with preview signing credentials. The label is the maintainer's sign-off
# that they reviewed that code, so it covers exactly the commit it was applied
# to: this pins that commit and consumes the label before running anything. A
# later push deploys nothing until the label is applied again.
#
# Same-repo PRs deploy on every push while labeled, via mobile-eas-preview.yml.

on:
pull_request_target:
types: [labeled]

permissions:
contents: read

jobs:
preview:
name: EAS Preview
if: >-
github.event.pull_request.head.repo.full_name != github.repository &&
github.event.label.name == '🚀 Mobile Continuous Deployment'
runs-on: blacksmith-8vcpu-ubuntu-2404
concurrency:
group: mobile-eas-preview-fork-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
env:
APP_VARIANT: preview
NODE_OPTIONS: --max-old-space-size=8192
MOBILE_VERSION_POLICY: fingerprint
steps:
# If a maintainer removed the label first, that removal wins.
- id: consume
name: Consume the deployment label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
name: context.payload.label.name,
});
core.setOutput("consumed", "true");
} catch (error) {
if (error.status !== 404) throw error;
core.info("The label was removed before this run could consume it. Skipping.");
core.setOutput("consumed", "false");
}
- id: expo-token
name: Check for EXPO_PREVIEW_TOKEN
if: steps.consume.outputs.consumed == 'true'
env:
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
run: |
if [ -n "$EXPO_TOKEN" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "EXPO_PREVIEW_TOKEN is not set; skipping EAS preview."
fi
# The head SHA from the label event, not the branch, so a push after the
# label cannot swap in unreviewed code. No credentials stay in .git for
# the PR's scripts to read.
- name: Checkout reviewed commit
if: steps.expo-token.outputs.present == 'true'
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: false
# No sparse-checkout: see mobile-eas-preview.yml.

# No caches: pull_request_target saves them under main's scope, where
# trusted workflows with other secrets would restore what PR code wrote.
- name: Setup Vite+
if: steps.expo-token.outputs.present == 'true'
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: false
run-install: |
args:
- --filter=@t3tools/mobile...
- name: Expose pnpm
if: steps.expo-token.outputs.present == 'true'
run: |
pnpm_version="$(node --print "require('./package.json').packageManager.split('@').pop()")"
vp_pnpm_bin="$HOME/.vite-plus/package_manager/pnpm/$pnpm_version/pnpm/bin"
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version
- name: Setup EAS
if: steps.expo-token.outputs.present == 'true'
uses: expo/expo-github-action@v8
with:
# Pinned and installed with npm for the same reasons as mobile-eas-preview.yml.
eas-version: 21.8.0
eas-cache: false
token: ${{ secrets.EXPO_PREVIEW_TOKEN }}
packager: npm

- name: Pull preview environment variables
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
run: |
eas env:pull preview --non-interactive
# See mobile-eas-preview.yml: keeps Android build and OTA fingerprints matching.
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (googleServicesFile) {
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
}
NODE
- name: Deploy with fingerprint check
if: steps.expo-token.outputs.present == 'true'
uses: expo/expo-github-action/continuous-deploy-fingerprint@main
env:
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
RUNNER_DEBUG: "1"
# The action only comments for pull_request events; this hook gives
# it the PR under pull_request_target. Without it, it skips the comment.
EXPO_TEST_GITHUB_PULL: ${{ github.event.pull_request.number }}
with:
profile: preview:dev
branch: pr-${{ github.event.pull_request.number }}
platform: all
environment: preview
working-directory: apps/mobile
github-token: ${{ secrets.GITHUB_TOKEN }}
14 changes: 8 additions & 6 deletions .github/workflows/mobile-eas-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ on:
jobs:
preview:
name: EAS Preview
# Fork PRs deploy one reviewed commit per label through mobile-eas-preview-fork.yml.
if: |
github.event.pull_request.head.repo.full_name == github.repository &&
contains(github.event.pull_request.labels.*.name, '🚀 Mobile Continuous Deployment') &&
(github.event.action != 'labeled' || github.event.label.name == '🚀 Mobile Continuous Deployment')
runs-on: blacksmith-8vcpu-ubuntu-2404
Expand All @@ -23,15 +25,15 @@ jobs:
MOBILE_VERSION_POLICY: fingerprint
steps:
- id: expo-token
name: Check for EXPO_TOKEN
name: Check for EXPO_PREVIEW_TOKEN
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
run: |
if [ -n "$EXPO_TOKEN" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "EXPO_TOKEN is not available; skipping EAS preview."
echo "EXPO_PREVIEW_TOKEN is not available; skipping EAS preview."
fi
- name: Checkout
Expand Down Expand Up @@ -70,7 +72,7 @@ jobs:
# continuous-deploy-fingerprint still reads `build.project.ownerAccount`.
# Unpin once https://github.com/expo/expo-github-action/issues/358 is fixed.
eas-version: 21.8.0
token: ${{ secrets.EXPO_TOKEN }}
token: ${{ secrets.EXPO_PREVIEW_TOKEN }}
# npm, not pnpm: this only installs eas-cli into the action's own
# tool dir, and pnpm 11 hard-fails that install on dtrace-provider's
# ignored build script (no allowBuilds config outside the repo).
Expand All @@ -80,7 +82,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
run: |
eas env:pull preview --non-interactive
# EAS Update disables dotenv loading. Keep the downloaded native config
Expand All @@ -99,7 +101,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true'
uses: expo/expo-github-action/continuous-deploy-fingerprint@main
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PREVIEW_TOKEN }}
# The Expo action otherwise suppresses the EAS command's failure output.
RUNNER_DEBUG: "1"
with:
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/mobile-eas-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ jobs:
- id: expo-token
name: Check for EXPO_TOKEN
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
run: |
if [ -n "$EXPO_TOKEN" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "EXPO_TOKEN is not available; skipping EAS production job."
echo "EXPO_PRODUCTION_TOKEN is not available; skipping EAS production job."
fi
- id: version_app_token
Expand Down Expand Up @@ -149,7 +149,7 @@ jobs:
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
token: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
# npm, not pnpm: this only installs eas-cli into the action's own
# tool dir, and pnpm 11 hard-fails that install on dtrace-provider's
# ignored build script (no allowBuilds config outside the repo).
Expand All @@ -159,7 +159,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
run: |
eas env:pull production --non-interactive
# EAS commands skip dotenv, and the Android fingerprint hashes this
Expand Down Expand Up @@ -244,7 +244,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true' && github.event_name == 'workflow_dispatch' && inputs.mode == 'build'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
MOBILE_BUILD_PROFILE: ${{ inputs.profile || 'production' }}
run: eas build --platform ${{ inputs.platform }} --profile "$MOBILE_BUILD_PROFILE" --auto-submit --non-interactive --no-wait

Expand All @@ -255,7 +255,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true' && (github.event_name == 'push' || inputs.mode == 'update')
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
PLATFORM: ${{ inputs.platform || 'all' }}
run: |
if [ "$PLATFORM" = "all" ]; then platforms="ios android"; else platforms="$PLATFORM"; fi
Expand All @@ -275,7 +275,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true' && github.event_name == 'workflow_dispatch' && inputs.mode == 'update'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
MOBILE_BUILD_PROFILE: ${{ inputs.profile || 'production' }}
OTA_MESSAGE: ${{ inputs.message || format('Production OTA ({0})', github.sha) }}
run: |
Expand All @@ -299,7 +299,7 @@ jobs:
if: steps.expo-token.outputs.present == 'true' && github.event_name == 'push'
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EXPO_TOKEN: ${{ secrets.EXPO_PRODUCTION_TOKEN }}
run: |
failed=0
message="$(git log -1 --pretty=%s | head -c 120) ($(git rev-parse --short=9 HEAD))"
Expand Down
7 changes: 5 additions & 2 deletions apps/mobile/src/features/review/reviewModel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { parsePatchFiles } from "@pierre/diffs/utils/parsePatchFiles";
import type { ChangeTypes, FileDiffMetadata } from "@pierre/diffs/types";
import type { ThreadCheckpointSummary } from "@t3tools/client-runtime/state/thread-checkpoints";
import {
checkpointFileTotals,
type ThreadCheckpointSummary,
} from "@t3tools/client-runtime/state/thread-checkpoints";
import type { ReviewDiffPreviewSource } from "@t3tools/contracts";
import { quoteGitPatchPath } from "@t3tools/shared/gitPatchPath";
import * as Arr from "effect/Array";
Expand Down Expand Up @@ -107,7 +110,7 @@ function checkpointTitle(checkpoint: ThreadCheckpointSummary): string {
}

function checkpointSubtitle(checkpoint: ThreadCheckpointSummary): string {
const fileCount = checkpoint.files.length;
const fileCount = checkpointFileTotals(checkpoint).count;
if (checkpoint.status !== "ready") {
return `Diff ${checkpoint.status}`;
}
Expand Down
15 changes: 12 additions & 3 deletions apps/mobile/src/features/settings/EnvironmentRoutesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
isLearned,
} from "@t3tools/client-runtime/connection";
import { type EnvironmentId, sessionGrantsScope } from "@t3tools/contracts";
import { AUTH_SCOPE_OPTIONS } from "@t3tools/shared/authScopeOptions";
import {
AUTH_SCOPE_OPTIONS,
sessionPermissionsUnavailableMessage,
} from "@t3tools/shared/authScopeOptions";
import { AsyncResult } from "effect/reactivity";
import * as Option from "effect/Option";
import { useState } from "react";
Expand Down Expand Up @@ -163,8 +166,14 @@ export function EnvironmentRoutesSection({
</>
) : (
<Text className="text-sm text-foreground-muted">
Permissions not checked. Connect to this environment to view this session’s
permissions.
{sessionPermissionsUnavailableMessage({
connected: activeRouteId !== null,
failed: sessionResult._tag === "Failure",
rejected:
sessionResult._tag !== "Failure" &&
!sessionResult.waiting &&
session?.authenticated === false,
})}
</Text>
)}
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type ServerProvider,
} from "@t3tools/contracts";
import { settlePromise, squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
import { sessionPermissionsUnavailableMessage } from "@t3tools/shared/authScopeOptions";
import * as Cause from "effect/Cause";
import * as Exit from "effect/Exit";
import { AsyncResult } from "effect/reactivity";
Expand Down Expand Up @@ -208,11 +209,14 @@ function EnvironmentDetail({ environmentId }: { readonly environmentId: Environm
</Text>
) : !allowed ? (
<Text className="px-2 text-sm text-foreground-muted">
{AsyncResult.isFailure(sessionResult)
? "Could not verify your permissions. Reconnect to try again."
: session === null
? "Checking permissions…"
: "This connection does not have permission to manage the environment."}
{AsyncResult.isFailure(sessionResult) || session === null || !session.authenticated
? sessionPermissionsUnavailableMessage({
connected,
failed: AsyncResult.isFailure(sessionResult),
rejected:
!AsyncResult.isFailure(sessionResult) && session?.authenticated === false,
})
: "This connection does not have permission to manage the environment."}
</Text>
) : null}
{error ? (
Expand Down
Loading
Loading