Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
be1a836
chore(release): prepare v0.0.32
t3-code[bot] Aug 7, 2026
6d70e6d
fix(mobile): reconnects no longer shift the thread list (#5372)
t3dotgg Aug 7, 2026
5661c61
feat(web): drag pinned threads into your own order (#5581)
t3dotgg Aug 7, 2026
e2cd238
chore(ci): vouch StiensWout (#5637)
t3-code[bot] Aug 7, 2026
72d673a
feat(desktop): remember recently used sites in the Browser panel (#5270)
chrisdeeming Aug 7, 2026
82406bc
chore: vouch chrisdeeming (#5641)
t3-code[bot] Aug 7, 2026
f0fb406
feat(web): make sidebar artwork theme-aware (#5636)
maria-rcks Aug 7, 2026
7a84f6c
fix(web): reconnect the composer seam for remote non-Git projects (#5…
caezium Aug 7, 2026
45d9aa9
fix(web): show Stop button while input is pending (#5554)
ipanasenko Aug 7, 2026
31891a1
feat(web): fold plan mode and token-by-token output into Legacy featu…
t3dotgg Aug 8, 2026
0de9540
feat: sidebar v2 is now the default sidebar (#5672)
t3dotgg Aug 8, 2026
4eaf5ef
fix(server): stop PR status lookups amplifying GitHub rate limits (#5…
t3dotgg Aug 8, 2026
ed886fe
fix(web): delay transient reconnect warnings (#5670)
t3-code[bot] Aug 8, 2026
daf8ee0
fix(web): inherit terminal size in simple typography (#5628)
chrisdeeming Aug 8, 2026
2c7267a
fix(server): stop the reaper from silently killing live background su…
t3dotgg Aug 8, 2026
0640410
fix(desktop): zoom shortcuts no longer die when the preview browser h…
t3dotgg Aug 8, 2026
30164cb
feat(mobile): one sheet for model and thread settings (#5625)
t3dotgg Aug 8, 2026
8101cd0
feat(usage): usage page reading provider transcripts across environme…
t3dotgg Aug 8, 2026
a20923c
fix(web): usage chart no longer makes Claude look like the bigger spe…
t3dotgg Aug 8, 2026
89ee692
fix(web): persist diff view mode (#5731)
leorivastech Aug 8, 2026
c2f8cb7
feat(web): show how many subagents are running at a glance (#5745)
t3dotgg Aug 8, 2026
be01b28
fix(web): add missing cursor-pointer styling to dropdowns and interac…
naMqe-h Aug 9, 2026
e70cdb4
fix(server): stop Claude resume handshakes from completing turns that…
gfsaaser24 Aug 9, 2026
49964e3
chore: vouch gfsaaser24 (#5761)
t3dotgg Aug 9, 2026
7b2cf43
chore: vouch saphid (#5763)
t3dotgg Aug 9, 2026
89c320d
fix(server): stop Codex threads with queued follow-ups (#5762)
t3dotgg Aug 9, 2026
70c423a
fix(web): usage page loses the cost quality panel, gains a back butto…
t3dotgg Aug 9, 2026
a6c9b41
feat(server): agents can now open the images you paste into chat (#5757)
t3dotgg Aug 9, 2026
5208bde
fix(web): pinned reorder no longer reshuffles while writes land (#5767)
t3dotgg Aug 9, 2026
288d8e3
feat(web): overhaul project settings into a real settings page (#5768)
t3dotgg Aug 9, 2026
886195e
fix(web): usage totals no longer jump while devices report in (#5772)
t3dotgg Aug 9, 2026
5bb8c03
fix(server): settle no longer leaves monitors and dev servers running…
t3dotgg Aug 9, 2026
6dbffa0
feat: pick worktree or current checkout per project (#5766)
t3dotgg Aug 9, 2026
6f69b44
fix(web): sidebar rows show the branch again, not a truncated plan st…
t3dotgg Aug 9, 2026
ddaa6af
feat(server): vp run migrate-dev-db seeds worktree dev dbs with real …
t3dotgg Aug 9, 2026
05eb051
feat(web): keep unsent drafts one click away in the sidebar (#5777)
t3dotgg Aug 9, 2026
076e904
feat(web): project icons can be chosen manually (#5775)
t3dotgg Aug 9, 2026
ba9c9ae
fix(server): one greedy agent process no longer takes down the whole …
t3dotgg Aug 9, 2026
963ebf5
ci: label-gated hosted-web preview deploys (#5465)
t3dotgg Aug 9, 2026
1a003e3
Add cross-platform mobile usage dashboard (#5743)
juliusmarminge Aug 9, 2026
285cf59
fix(web): preserve desktop route during Clerk auth (#5770)
wobsoriano Aug 9, 2026
02f4ce5
fix(web): match create theme and import theme buttons to the standard…
UtkarshUsername Aug 9, 2026
deb901d
fix(server): favicon resolution no longer pins the event loop (#5538)
murenovich Aug 9, 2026
062b461
fix(shared): bound the file-link label so bracket runs stop rescannin…
tsouth89 Aug 9, 2026
659986c
fix(web): thread title button no longer eats the drag area (#5857)
nathangerday Aug 9, 2026
bd18d8d
fix(web): unify usage page chrome (#5823)
t3-code[bot] Aug 9, 2026
c8ad4b8
fix(shell): add ~/.local/bin to the Windows CLI resolver so native-in…
arhxam Aug 9, 2026
83d769f
fix(web): match settings search shortcut styling to command palette's…
UtkarshUsername Aug 9, 2026
f993fa1
fix(mobile): long-pressing a thread row no longer navigates into the …
juliusmarminge Aug 9, 2026
0d38866
fix(server): usage no longer double-counts forked Codex sessions (#5887)
t3dotgg Aug 9, 2026
2abe668
fix(server): sandbox user-provided SVGs (#5916)
t3dotgg Aug 9, 2026
9a1472d
fix(web): match usage titlebar text styling (#5897)
t3-code[bot] Aug 10, 2026
f21d5e4
Move project settings to contextual project routes (#5923)
juliusmarminge Aug 10, 2026
5da4533
Preserve back navigation when opening settings (#5930)
juliusmarminge Aug 10, 2026
73b2e8f
Automate production mobile EAS releases (#5609)
juliusmarminge Aug 10, 2026
9690680
Add settings and usage breadcrumbs (#5929)
juliusmarminge Aug 10, 2026
cbd55d6
fix(web): correct model picker trigger padding (#5935)
Chrono-byte Aug 10, 2026
0ca9fb3
fix(web): show worktree icon in sidebar v2 (#5909)
tris203 Aug 10, 2026
ef051bd
fix(web): enable restore defaults after theme mix changes (#5928)
Lucenx9 Aug 10, 2026
d432100
fix(web): trait menu closes after you pick a level (#5879)
t3dotgg Aug 10, 2026
f0e2975
fix(web): align project name with headline (#5864)
carterwsmith Aug 10, 2026
3d74474
fix(web): update pills use readable theme foregrounds (#5938)
chrisdeeming Aug 10, 2026
9821bca
fix(web): use themed confirmation dialogs (#5624)
StiensWout Aug 10, 2026
fbcae59
fix(web): use import/export-appropriate icons for theme buttons (#5964)
UtkarshUsername Aug 10, 2026
67ef189
fix(mobile): detect PowerShell cmdlet errors in work log rows (#5726)
myacoub91 Aug 10, 2026
d440442
fix(mobile): stop Android user bubbles with code blocks from overlapp…
Brechard Aug 10, 2026
3b72d17
fix(mobile): parse EAS fingerprint JSON (#5991)
juliusmarminge Aug 10, 2026
78f462c
chore(release): prepare v0.0.33
t3-code[bot] Aug 10, 2026
cad2c93
feat: multi-provider pull requests page with in-app reviews (#4849)
Bil0000 Aug 10, 2026
8de0aa2
perf(desktop): probe the Windows shell environment concurrently (#5878)
tsouth89 Aug 10, 2026
b91a000
feat(web): add duplicate action to the T3 Code default theme (#6013)
UtkarshUsername Aug 10, 2026
0a7c662
fix(web): improve built-in theme contrast (#6000)
chrisdeeming Aug 10, 2026
1b120f3
fix(ci): extend release publish timeout (#6034)
t3-code[bot] Aug 10, 2026
428d9f9
Allow Android tablets to rotate (#5613)
juliusmarminge Aug 10, 2026
a7b0366
fix(web): account for Windows window controls in PR page header (#6049)
UtkarshUsername Aug 10, 2026
9afef94
feat: add three-hour snooze option (#5914)
Noojuno Aug 10, 2026
e5c82d7
fix(mobile): keep chat composer above the Android gesture bar (#5988)
PollyGlot Aug 11, 2026
73ba16e
merge(main): bring fork main up to upstream main e5c82d79a
Aug 11, 2026
b13cd60
docs(seam): register the reaper wedge cap; retitle SidebarV2 -> Sidebar
Aug 11, 2026
ba4f58d
fix(server): provide ServerConfig to fork provider tests; pin reaper …
Aug 11, 2026
941fd22
ci: run new upstream workflows on GitHub-hosted runners in forks
Aug 11, 2026
24c6c22
ci(release): drop the upstream CLI npm publish job per fork policy
Aug 11, 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
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
# Keep entries sorted alphabetically.
github:adityavardhansharma
github:binbandit
github:chrisdeeming
github:chuks-qua
github:cursoragent
github:gbarros-dev
github:gfsaaser24
github:github-actions[bot]
github:hwanseoc
github:jamesx0416
Expand All @@ -25,7 +27,9 @@ github:Noojuno
github:notkainoa
github:PatrickBauer
github:realAhmedRoach
github:saphid
github:shiroyasha9
github:StiensWout
github:Yash-Singh1
github:eggfriedrice24
github:Ymit24
Expand Down
108 changes: 103 additions & 5 deletions .github/workflows/mobile-eas-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ name: Mobile EAS Production
# in the same OS/pnpm as the EAS build; a macOS `eas build` computes a different
# fingerprint (platform-specific deps + pnpm version) and errors. On this Linux
# 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 with --auto-submit (TestFlight +
# Play internal track). Bumping `version` is therefore 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. Releasing to the App Store stays a manual
# App Store Connect step.
# 2. OTA: publish a production-channel update for each platform where at
# least one finished production build matches the current native
# fingerprint. Old-version binaries with a matching fingerprint receive
# it too. 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.
# workflow_dispatch remains as a manual override for both modes (e.g. to
# retry an errored build or force an OTA).
on:
workflow_dispatch:
inputs:
Expand All @@ -29,10 +46,30 @@ on:
description: "OTA update message (mode=update only)"
required: false
type: string
push:
branches: [main]
paths:
- apps/mobile/**
- packages/client-runtime/**
- packages/contracts/**
- packages/shared/**
- assets/**
- scripts/**
- patches/**
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .github/workflows/mobile-eas-production.yml

# Serialize runs so OTAs publish in merge order. GitHub keeps at most one
# queued run per group, so a burst of merges collapses into one run of the
# newest commit — intermediate commits don't need their own OTA.
concurrency:
group: mobile-eas-production
cancel-in-progress: false

jobs:
production:
name: EAS Production ${{ inputs.mode }}
name: EAS Production ${{ github.event_name == 'push' && 'auto' || inputs.mode }}
runs-on: ubuntu-24.04
permissions:
contents: read
Expand Down Expand Up @@ -98,15 +135,15 @@ jobs:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
run: eas env:pull production --non-interactive

- name: Build and submit
if: steps.expo-token.outputs.present == 'true' && inputs.mode == 'build'
- name: Build and submit (manual)
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 }}
run: eas build --platform ${{ inputs.platform }} --profile production --auto-submit --non-interactive --no-wait

- name: Publish OTA update
if: steps.expo-token.outputs.present == 'true' && inputs.mode == 'update'
- name: Publish OTA update (manual)
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 }}
Expand All @@ -117,3 +154,64 @@ jobs:
--platform ${{ inputs.platform }} \
--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. Builds started here stay attached to this serialized run so
# the queued run for a later merge cannot overtake them and lose its OTA.
# 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
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; then
echo ":building_construction: $platform: cut production build for $version (auto-submitted)" >> "$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 \
--channel production \
--environment production \
--platform "$platform" \
--message "$message" \
--non-interactive
echo ":white_check_mark: $platform: OTA published to production (fingerprint \`$hash\`)" >> "$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"
fi
done

- name: Propagate store build failure
if: steps.store_builds.outcome == 'failure'
run: exit 1
205 changes: 205 additions & 0 deletions .github/workflows/mobile-fingerprint-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
name: Mobile Fingerprint Check

# Detects whether a PR changes the native fingerprint — i.e. whether merging
# it would leave main un-OTA-able until a new store build ships. Native-change
# PRs get the "📱 Native Change" label so they can be held and merged as a
# batch right before the next store submission, keeping main OTA-able for
# everything else in between. (Once one native PR merges, every later merge
# inherits the drifted fingerprint and loses OTA reach too — that is why the
# signal has to fire before merge, not after.)
#
# The check is advisory: it always passes, the label is the signal. Both
# fingerprints are computed in this one job (same OS, same corepack-pinned
# pnpm), so the comparison is self-consistent; no EXPO_TOKEN needed.
on:
pull_request:
paths:
- apps/mobile/**
- packages/client-runtime/**
- packages/contracts/**
- packages/shared/**
- assets/**
- scripts/**
- patches/**
- pnpm-lock.yaml
- pnpm-workspace.yaml
- .github/workflows/mobile-fingerprint-check.yml

concurrency:
group: mobile-fingerprint-check-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
fingerprint:
name: Native fingerprint diff
runs-on: ${{ github.repository == 'pingdotgg/t3code' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
permissions:
contents: read
issues: write
pull-requests: write
env:
APP_VARIANT: production
NODE_OPTIONS: --max-old-space-size=8192
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Default pull_request checkout is the merge commit (PR applied on
# top of base), so the "head" fingerprint is the state main would
# actually be in after merging — stale branches compare cleanly.
fetch-depth: 0

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: true
run-install: |
args:
- --filter=@t3tools/mobile...

- name: Expose pnpm
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: Fingerprint merge result
working-directory: apps/mobile
run: |
mkdir -p "$RUNNER_TEMP/fp/head" "$RUNNER_TEMP/fp/base"
for platform in ios android; do
npx expo-updates fingerprint:generate --platform "$platform" > "$RUNNER_TEMP/fp/head/$platform.json"
done

- name: Fingerprint base
run: |
git checkout --quiet "${{ github.event.pull_request.base.sha }}"
# Re-sync node_modules to the base commit's lockfile before
# fingerprinting — a dep-changing PR must not fingerprint the base
# against head's installed packages.
pnpm install --filter=@t3tools/mobile...
cd apps/mobile
for platform in ios android; do
npx expo-updates fingerprint:generate --platform "$platform" > "$RUNNER_TEMP/fp/base/$platform.json"
done

- id: compare
name: Compare fingerprints
run: |
changed=""
{
echo "## Native fingerprint diff"
echo
for platform in ios android; do
head_hash="$(jq -r .hash "$RUNNER_TEMP/fp/head/$platform.json")"
base_hash="$(jq -r .hash "$RUNNER_TEMP/fp/base/$platform.json")"
if [ "$head_hash" = "$base_hash" ]; then
echo "- ✅ **$platform**: unchanged (\`$head_hash\`) — OTA-compatible"
continue
fi
changed="$changed $platform"
echo "- 📱 **$platform**: \`$base_hash\` → \`$head_hash\` — merging requires a new native build before OTAs work again"
jq -r -n \
--slurpfile h "$RUNNER_TEMP/fp/head/$platform.json" \
--slurpfile b "$RUNNER_TEMP/fp/base/$platform.json" '
($b[0].sources | map({ (.filePath // .id): .hash }) | add // {}) as $bm
| $h[0].sources[]
| select($bm[(.filePath // .id)] != .hash)
| " - \(.type): `\(.filePath // .id)`"'
done
} >> "$GITHUB_STEP_SUMMARY"
echo "changed_platforms=${changed# }" >> "$GITHUB_OUTPUT"

- name: Sync native change label
# Fork PRs get a read-only token under pull_request; the check stays
# advisory there (summary only). This workflow must not move to
# pull_request_target — it installs and runs PR code.
if: github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v8
env:
CHANGED_PLATFORMS: ${{ steps.compare.outputs.changed_platforms }}
with:
script: |
const managedLabel = {
name: "📱 Native Change",
color: "d93f0b",
description:
"Changes the native fingerprint; merging blocks production OTAs until a new store build ships.",
};
const nativeChanged = (process.env.CHANGED_PLATFORMS ?? "").trim() !== "";
const issueNumber = context.payload.pull_request.number;

try {
const { data: existing } = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
});

if (
existing.color !== managedLabel.color ||
(existing.description ?? "") !== managedLabel.description
) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
color: managedLabel.color,
description: managedLabel.description,
});
}
} catch (error) {
if (error.status !== 404) {
throw error;
}

try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: managedLabel.name,
color: managedLabel.color,
description: managedLabel.description,
});
} catch (createError) {
if (createError.status !== 422) {
throw createError;
}
}
}

const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});
const hasLabel = currentLabels.some((label) => label.name === managedLabel.name);

if (nativeChanged && !hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [managedLabel.name],
});
} else if (!nativeChanged && hasLabel) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: managedLabel.name,
});
} catch (removeError) {
if (removeError.status !== 404) {
throw removeError;
}
}
}

core.info(
`PR #${issueNumber}: native fingerprint ${nativeChanged ? `changed (${process.env.CHANGED_PLATFORMS})` : "unchanged"}`,
);
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -691,10 +691,12 @@ jobs:

release:
name: Publish GitHub Release
# Fork policy: the upstream CLI-package publishing job is omitted
# entirely; the fork never publishes that package.
needs: [preflight, build]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.build.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
timeout-minutes: 30
permissions:
contents: write
steps:
Expand Down
Loading
Loading