Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
64fafbd
perf(web): speed up folder menu sorting (#10190)
AksharP5 Sep 6, 2026
29d03ec
style(web): fix inconsistencies in new settings layouts (#10177)
extoci Sep 6, 2026
2d645df
feat(threads): persist manual active thread order (#9729)
juliusmarminge Sep 6, 2026
6766e68
feat(mobile): arrange active threads from both thread lists (#9730)
juliusmarminge Sep 6, 2026
4023d93
feat(web): drag threads across sections with consistent motion (#9731)
juliusmarminge Sep 6, 2026
9a47c7b
feat(web): simplify sidebar drag destination cues (#9750)
juliusmarminge Sep 6, 2026
36c48a6
fix(mobile): keep pending tasks queued when a send fails in flight (#…
juliusmarminge Sep 6, 2026
c0bf354
feat(mobile): show new-task drafts alongside pending tasks in the thr…
juliusmarminge Sep 6, 2026
8e129a0
feat(mobile): allow several new-task drafts per project (#10327)
juliusmarminge Sep 6, 2026
9846915
fix(mobile): slide settled threads out before collapsing (#10345)
StiensWout Sep 6, 2026
95d9937
fix(clients): show feedback results in composer banners (#10398)
juliusmarminge Sep 6, 2026
ea646c0
fix(server): stop Windows terminal polling from spiking CPU (#9476)
UtkarshUsername Sep 6, 2026
c2c4185
fix(web): onboarding installs agents without needing Node or npm (#10…
t3dotgg Sep 6, 2026
7ac93e3
fix(server): allow settling threads with unanswered async questions (…
t3dotgg Sep 6, 2026
001f06d
feat(ci): ship stable releases from the latest nightly commit (#10410)
t3dotgg Sep 6, 2026
075a86e
feat(marketing): add a nightly channel to the download page (#10408)
t3dotgg Sep 6, 2026
9c96ac2
fix(web): keep settings inputs focused during IME composition (#10262)
Lucenx9 Sep 6, 2026
f1a0811
fix(server): preserve Codex reset credits during usage updates (#10308)
yashranaway Sep 6, 2026
e15ffb9
docs: link the repository security reporting policy (#10303)
yashranaway Sep 6, 2026
2d6a379
fix(web): only show auto balance errors after failed checks (#10407)
maria-rcks Sep 6, 2026
3941c2a
fix(web): improve preview recording frame delivery (#10403)
maria-rcks Sep 6, 2026
3cd2cbb
Modify model and budget settings in ui-consistency.md
juliusmarminge Sep 6, 2026
f5a1ec5
Update model and effort in effect-service-conventions
juliusmarminge Sep 6, 2026
0032892
Fix duplicate maxBudgetPerRun entry in UI consistency
juliusmarminge Sep 6, 2026
ac4f1a2
fix(server): preserve inline provider secrets on redacted saves (#10054)
maxwellyoung Sep 6, 2026
d57bdf3
Fix formatting of maxBudgetPerPR in conventions file
juliusmarminge Sep 6, 2026
2108996
fix(web, mobile): replace Apple desktop machine labels (#10396)
extoci Sep 6, 2026
7e8ae6b
fix(web): hide browser when the right panel starts closing (#10385)
Neel2107 Sep 6, 2026
4538770
fix(web): keep settings section headings description-free (#10415)
maria-rcks Sep 6, 2026
0a89364
fix(usage): read and redeem hub reset credits through CLIProxyAPI (#1…
juliusmarminge Sep 6, 2026
7939415
fix(web): deduplicate expanded tool labels and keep errors expandable…
Yash-Singh1 Sep 6, 2026
86070cb
fix(server): skip git status scans while the index is locked (#9845)
Gigioxx Sep 6, 2026
29c5ecd
fix(mcp): allow text-only preview snapshots (#10232)
juliusmarminge Sep 6, 2026
3d00cfd
fix(claude): name the expired login or usage limit instead of a gener…
vitalyiegorov Sep 6, 2026
66a24d6
feat(mobile): queue a message while its attachment is still uploading…
juliusmarminge Sep 6, 2026
d6aa179
feat(mobile): show when an existing thread has a message waiting in t…
juliusmarminge Sep 6, 2026
9513925
fix(codex): accept misalignment policy errors on thread resume (#10373)
realbakari Sep 6, 2026
d3d4ea4
fix(server): skip disabled settlement lookups (#10424)
t3dotgg Sep 6, 2026
9ab0635
fix(server): run OpenCode CLI commands sequentially (#10427)
t3dotgg Sep 7, 2026
5b0c923
feat(web): name the drop action while dragging sidebar threads (#10378)
SunkenInTime Sep 7, 2026
252df77
perf(web): keep the sidebar responsive during bulk thread updates (#1…
t3dotgg Sep 7, 2026
ec36176
fix(web): onboarding wizard now supports light mode (#10432)
t3dotgg Sep 7, 2026
7112697
feat(threads): dismiss async questions without replying (#10431)
t3dotgg Sep 7, 2026
a12589d
fix(web): stop collapsing the composer when it loses focus (#10437)
t3dotgg Sep 7, 2026
1abc717
fix(server): keep interrupted threads resumable after restarts (#10421)
maria-rcks Sep 7, 2026
efeac14
fix(web): show load balancing note for a single machine (#10433)
maria-rcks Sep 7, 2026
1e740e4
fix(server): follow placeholder branches after checkout updates (#10441)
Yash-Singh1 Sep 7, 2026
8c9a49a
fix(mobile): expand single-line tool details in work logs (#10442)
Yash-Singh1 Sep 7, 2026
95f9b14
fix(server): import transcripts with oversized tool records (#10430)
Yash-Singh1 Sep 7, 2026
0860cea
fix(marketing): deploy site with nightly releases (#10443)
t3dotgg Sep 7, 2026
a07715c
fix(web): preserve multiline composer drafts during timeline scrollin…
Yash-Singh1 Sep 7, 2026
52b2bf7
fix(server): handle JSON-wrapped titles and verbose Claude output (#1…
Noojuno Sep 7, 2026
da976cf
fix(marketing): restore continuous endorsement scrolling (#10450)
t3dotgg Sep 7, 2026
bd280de
Revert "fix(marketing): restore continuous endorsement scrolling" (#1…
t3dotgg Sep 7, 2026
b22646c
fix(marketing): bring back the endorsement marquee (#10455)
t3dotgg Sep 7, 2026
de28fa1
chore: enable CodeRabbit automatic reviews (#10457)
t3dotgg Sep 7, 2026
1c1d38f
fix(codex): keep Spark limits from replacing the main allowance (#10458)
Yash-Singh1 Sep 7, 2026
7e03dcf
fix(marketing): send 95 nightly downloads to the downloads page (#10460)
t3dotgg Sep 7, 2026
ecf3716
fix(web): composer regains focus when you tab back into T3 Code (#10463)
t3dotgg Sep 7, 2026
4e969f3
fix(web): keep sidebar drag dividers clear and gestures smooth (#10453)
juliusmarminge Sep 7, 2026
6134b90
fix(server): mark Cursor transport error answers as failed (#10337)
shivamhwp Sep 7, 2026
f5fb056
fix(web): clear stuck panel resize cursor (#10461)
t3dotgg Sep 7, 2026
490eb17
fix(web): clarify sidebar drag dividers and empty targets (#10464)
juliusmarminge Sep 7, 2026
f729e8f
fix(web): make onboarding a shared multi-computer wizard (#10465)
juliusmarminge Sep 7, 2026
6abdf37
fix(shared): redeem reset credits through the hub when it holds the a…
juliusmarminge Sep 7, 2026
e1230d6
fix(mobile): keep pending messages in the chat timeline (#10449)
juliusmarminge Sep 7, 2026
bb5748b
fix(mobile): show connection status in the floating pill instead of a…
juliusmarminge Sep 7, 2026
a7028f1
fix: use Pierre icons consistently for attachments (#10475)
juliusmarminge Sep 7, 2026
dc92e5b
merge: sync upstream through a7028f1399 (classic-merge)
github-actions[bot] Sep 7, 2026
7d4febd
fix(mobile): read flattened pending-task fields after draft weld
github-actions[bot] Sep 7, 2026
af0959c
fix(server): weld HashMap read models after upstream thread-order tests
github-actions[bot] Sep 7, 2026
7e74b0a
fix(server): keep activeOrderKey on projection thread rows
github-actions[bot] Sep 7, 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
2 changes: 1 addition & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
reviews:
review_status: false
auto_review:
enabled: false
enabled: true
8 changes: 8 additions & 0 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Security policy

Report security vulnerabilities affecting T3 Code or T3 Tools-operated infrastructure to
[security@ping.gg](mailto:security@ping.gg). Please do not disclose them publicly until we have had
a reasonable opportunity to investigate and remediate them.

See the [full security policy](https://t3.codes/security-policy) for reporting details, scope,
and safe harbor terms for good-faith research.
43 changes: 33 additions & 10 deletions .github/scripts/check-nightly-release.cjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
const MINIMUM_RELEASE_GAP_MS = 6 * 60 * 60 * 1000;

// Runs after the workflow acquires the nightly concurrency lock.
async function shouldReleaseNightly({ github, context, core, now = Date.now() }) {
const isNightlyTag = (tag) => /^v.*-nightly\./.test(tag) || tag.startsWith("nightly-v");

// Newest published nightly by publication time, or undefined when none exists.
async function findLatestNightly({ github, context }) {
const releases = await github.paginate(github.rest.repos.listReleases, {
...context.repo,
per_page: 100,
});
const lastNightly = releases
.filter(
(release) =>
!release.draft &&
release.published_at &&
(/^v.*-nightly\./.test(release.tag_name) || release.tag_name.startsWith("nightly-v")),
)
return releases
.filter((release) => !release.draft && release.published_at && isNightlyTag(release.tag_name))
.sort((a, b) => Date.parse(b.published_at) - Date.parse(a.published_at))[0];
}

// Runs after the workflow acquires the nightly concurrency lock.
async function shouldReleaseNightly({ github, context, core, now = Date.now() }) {
const lastNightly = await findLatestNightly({ github, context });

if (!lastNightly) {
core.info("No published nightly found. Proceeding with release.");
Expand Down Expand Up @@ -41,4 +43,25 @@ async function shouldReleaseNightly({ github, context, core, now = Date.now() })
return true;
}

module.exports = { shouldReleaseNightly };
// Stable releases build the commit the latest nightly shipped, so the stable
// build is one nightly users already ran. Returns the nightly tag, its commit,
// and the stable version that nightly was a preview of.
async function resolveLatestNightlyCommit({ github, context, core }) {
const lastNightly = await findLatestNightly({ github, context });
if (!lastNightly) {
throw new Error("No published nightly found. Stable releases build the latest nightly commit.");
}

const tag = lastNightly.tag_name;
// repos.getCommit dereferences annotated tags, so this is the commit either way.
const { data: commit } = await github.rest.repos.getCommit({ ...context.repo, ref: tag });
const version = /^(?:nightly-)?v(\d+\.\d+\.\d+)-nightly\./.exec(tag)?.[1];
if (!version) {
throw new Error(`Cannot derive a stable version from nightly tag ${tag}.`);
}

core.info(`Latest nightly ${tag} shipped ${commit.sha} as a preview of ${version}.`);
return { tag, sha: commit.sha, version };
}

module.exports = { shouldReleaseNightly, resolveLatestNightlyCommit };
42 changes: 42 additions & 0 deletions .github/scripts/check-nightly-release.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,45 @@ for (const status of ["behind", "diverged"]) {
assert.equal(await shouldReleaseNightly(options), false);
});
}

const { resolveLatestNightlyCommit } = require("./check-nightly-release.cjs");

function nightlyCommitFixture({ releases, commitSha = "abc123" }) {
const refs = [];
const { options } = fixture({ releases });
options.github.rest.repos.getCommit = async ({ ref }) => {
refs.push(ref);
return { data: { sha: commitSha } };
};
return { options, refs };
}

test("stable releases resolve the commit of the newest published nightly", async () => {
const { options, refs } = nightlyCommitFixture({
releases: [
nightly(10, { tag_name: "v1.0.1-nightly.20260905.100" }),
nightly(1, { tag_name: "v1.0.1-nightly.20260905.123" }),
nightly(0, { tag_name: "v1.0.0" }),
nightly(0, { draft: true, tag_name: "v1.0.1-nightly.20260905.999" }),
],
commitSha: "deadbeef",
});
assert.deepEqual(await resolveLatestNightlyCommit(options), {
tag: "v1.0.1-nightly.20260905.123",
sha: "deadbeef",
version: "1.0.1",
});
assert.deepEqual(refs, ["v1.0.1-nightly.20260905.123"]);
});

test("stable releases derive the version from legacy nightly tags", async () => {
const { options } = nightlyCommitFixture({
releases: [nightly(1, { tag_name: "nightly-v0.9.0-nightly.20260905.5" })],
});
assert.equal((await resolveLatestNightlyCommit(options)).version, "0.9.0");
});

test("stable releases fail without a published nightly", async () => {
const { options } = nightlyCommitFixture({ releases: [nightly(0, { tag_name: "v1.0.0" })] });
await assert.rejects(resolveLatestNightlyCommit(options), /No published nightly/);
});
135 changes: 105 additions & 30 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:
- stable
- nightly
version:
description: "Release version (for example 1.2.3 or v1.2.3)"
description: "Stable version override (for example 1.2.3). Defaults to the version the latest nightly previewed."
required: false
type: string
artifacts_only:
Expand All @@ -44,33 +44,54 @@ permissions:
id-token: none

jobs:
check_changes:
name: Check automatic nightly release
if: github.event_name == 'schedule'
# Picks the commit every later job builds. Nightlies and tag pushes build the
# triggering commit. Manual stable releases build the commit of the latest
# published nightly, so stable only ever ships a build that nightly users
# have already run. Scheduled runs also decide here whether a nightly is due.
resolve_commit:
name: Resolve release commit
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
has_changes: ${{ steps.check.outputs.result }}
ref: ${{ steps.resolve.outputs.ref }}
nightly_version: ${{ steps.resolve.outputs.nightly_version }}
has_changes: ${{ steps.resolve.outputs.has_changes }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: .github/scripts

- id: check
name: Check release gap and new commits
- id: resolve
name: Resolve release commit
uses: actions/github-script@v8
env:
DISPATCH_CHANNEL: ${{ inputs.channel }}
with:
script: |
const { shouldReleaseNightly } = require('./.github/scripts/check-nightly-release.cjs');
return await shouldReleaseNightly({ github, context, core });
const {
shouldReleaseNightly,
resolveLatestNightlyCommit,
} = require('./.github/scripts/check-nightly-release.cjs');

if (context.eventName === 'schedule') {
core.setOutput('has_changes', await shouldReleaseNightly({ github, context, core }));
core.setOutput('ref', context.sha);
} else if (context.eventName === 'workflow_dispatch' && process.env.DISPATCH_CHANNEL !== 'nightly') {
const { tag, sha, version } = await resolveLatestNightlyCommit({ github, context, core });
core.notice(`Stable release builds ${sha}, the commit shipped by ${tag}.`);
core.setOutput('ref', sha);
core.setOutput('nightly_version', version);
} else {
core.setOutput('ref', context.sha);
}

preflight:
name: Preflight
needs: [check_changes]
needs: [resolve_commit]
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
needs.resolve_commit.result == 'success' &&
(github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true')
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
Expand All @@ -83,11 +104,12 @@ jobs:
cli_dist_tag: ${{ steps.release_meta.outputs.cli_dist_tag }}
is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }}
make_latest: ${{ steps.release_meta.outputs.make_latest }}
ref: ${{ github.sha }}
ref: ${{ needs.resolve_commit.outputs.ref }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.resolve_commit.outputs.ref }}
fetch-depth: 0
sparse-checkout: |
/*
Expand All @@ -109,8 +131,9 @@ jobs:
env:
DISPATCH_CHANNEL: ${{ github.event.inputs.channel }}
DISPATCH_VERSION: ${{ github.event.inputs.version }}
NIGHTLY_VERSION: ${{ needs.resolve_commit.outputs.nightly_version }}
NIGHTLY_DATE: ${{ github.run_started_at }}
NIGHTLY_SHA: ${{ github.sha }}
NIGHTLY_SHA: ${{ needs.resolve_commit.outputs.ref }}
NIGHTLY_RUN_NUMBER: ${{ github.run_number }}
run: |
if [[ "${GITHUB_EVENT_NAME}" == "schedule" || ( "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${DISPATCH_CHANNEL:-stable}" == "nightly" ) ]]; then
Expand All @@ -128,9 +151,9 @@ jobs:
echo "make_latest=false" >> "$GITHUB_OUTPUT"
else
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
raw="${DISPATCH_VERSION}"
raw="${DISPATCH_VERSION:-$NIGHTLY_VERSION}"
if [[ -z "$raw" ]]; then
echo "workflow_dispatch stable releases require the version input." >&2
echo "workflow_dispatch stable releases need a version input or a published nightly." >&2
exit 1
fi
else
Expand Down Expand Up @@ -215,14 +238,12 @@ jobs:

relay_public_config:
name: Resolve T3 Connect public config
# Consumes only the commit SHA, not preflight's resolved version, so it runs
# alongside preflight instead of after it. The condition mirrors preflight's:
# check_changes is skipped on manual and tag releases (skipped is neither failure
# nor success, so success() would be wrong here).
needs: [check_changes]
# Consumes only the release commit, not preflight's resolved version, so it
# runs alongside preflight instead of after it. The condition mirrors preflight's.
needs: [resolve_commit]
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
needs.resolve_commit.result == 'success' &&
(github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true')
runs-on: ubuntu-24.04
timeout-minutes: 5
environment:
Expand All @@ -244,7 +265,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
ref: ${{ needs.resolve_commit.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
Expand Down Expand Up @@ -317,19 +338,19 @@ jobs:
# machine. node-pty is N-API, so one binary works across all WSL Node versions.
build_wsl_node_pty:
name: Build WSL node-pty (linux-x64)
# Same gating as relay_public_config: only the commit SHA is needed, so this
# runs alongside preflight. See the condition comment there.
needs: [check_changes]
# Same gating as relay_public_config: only the release commit is needed, so
# this runs alongside preflight. See the condition comment there.
needs: [resolve_commit]
if: |
!failure() && !cancelled() &&
(github.event_name != 'schedule' || needs.check_changes.outputs.has_changes == 'true')
needs.resolve_commit.result == 'success' &&
(github.event_name != 'schedule' || needs.resolve_commit.outputs.has_changes == 'true')
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
ref: ${{ needs.resolve_commit.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
Expand Down Expand Up @@ -1042,6 +1063,60 @@ jobs:
"${vercel_scope_args[@]}"
fi

deploy_marketing:
name: Deploy marketing site
needs: [preflight, release]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'nightly' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.preflight.outputs.ref }}
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

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

- name: Deploy marketing site to Vercel
shell: bash
run: |
set -euo pipefail

if [[ -z "${VERCEL_TOKEN:-}" || -z "${VERCEL_ORG_ID:-}" ]]; then
echo "Missing one or more required Vercel secrets: VERCEL_TOKEN, VERCEL_ORG_ID." >&2
exit 1
fi

VERCEL_PROJECT_ID="$(
curl --fail --silent --show-error \
--header "Authorization: Bearer $VERCEL_TOKEN" \
"https://api.vercel.com/v9/projects/t3code-marketing?teamId=$VERCEL_ORG_ID" \
| jq --exit-status --raw-output '.id'
)"
export VERCEL_PROJECT_ID

vp dlx vercel@53.1.1 deploy \
--archive=tgz \
--prod \
--yes \
--token "$VERCEL_TOKEN" \
--scope "${VERCEL_TEAM_SLUG:-$VERCEL_ORG_ID}"

finalize:
name: Finalize release
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }}
Expand Down
5 changes: 3 additions & 2 deletions .macroscope/check-run-agents/effect-service-conventions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Effect Service Conventions
model: claude-opus-5
effort: high
model: gpt-5-6-sol
effort: medium
input: full_diff
tools:
- browse_code
Expand All @@ -16,6 +16,7 @@ labels:
- vouch:trusted
requires:
- Check
maxBudgetPerRun: 5
maxBudgetPerPR: 25
conclusion: failure
showToolCalls: true
Expand Down
6 changes: 3 additions & 3 deletions .macroscope/check-run-agents/ui-consistency.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: UI Consistency
model: claude-opus-5
model: gpt-5-6-terra
effort: medium
input: full_diff
tools:
Expand All @@ -15,9 +15,9 @@ labels:
- vouch:trusted
requires:
- Check
maxBudgetPerPR: 25
maxBudgetPerRun: 2
maxBudgetPerPR: 10
conclusion: failure
maxBudgetPerRun: 10
---

# UI consistency review
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const clientSettings: ClientSettings = {
confirmWorktreeRemoval: true,
confirmThreadUnpin: false,
contextWindowMeterEnabled: false,
composerCollapseOnBlur: false,
composerCollapseOnScroll: true,
dismissedProviderUpdateNotificationKeys: [],
diffIgnoreWhitespace: true,
Expand Down
Loading
Loading