Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d7c52f9
fix(mobile): the thread feed re-wraps once when an iOS column resizes…
juliusmarminge Nov 9, 2026
6b02cbf
fix(speech): match reference benchmarks to transcription hardware (#3…
UtkarshUsername Nov 9, 2026
12f3244
feat(models): add Claude Fable 5.2 (#33500)
t3dotgg Nov 9, 2026
f6203ff
fix(mobile): the chat inset resets after the keyboard closes while fr…
juliusmarminge Nov 9, 2026
7078dcb
fix(mobile): a full swipe on a thread row finishes even if the pan is…
juliusmarminge Nov 9, 2026
077fd62
fix(client): finishing a long background thread no longer freezes the…
juliusmarminge Nov 9, 2026
d0f7a50
feat(models): the model picker checks for new models when it opens (#…
t3dotgg Nov 9, 2026
206a8e6
fix(desktop): close focused DevTools with Cmd+W (#33518)
Yash-Singh1 Nov 9, 2026
1676313
chore(mobile): update react-native-pdf-jsi to 6.1.0 and link it on An…
juliusmarminge Nov 9, 2026
2da1b74
fix(mobile): the unfolded Duo sidebar no longer pops back in while re…
juliusmarminge Nov 9, 2026
eb6e7fa
fix(models): new models also surface the CLI update that supports the…
t3dotgg Nov 9, 2026
6e4711d
fix(web): clicked workspace areas no longer show the focus ring after…
t3dotgg Nov 9, 2026
bf469bb
chore(server): update Claude Agent SDK to 0.3.321 (#33530)
t3dotgg Nov 9, 2026
e85e009
fix(web): checking for updates finds provider updates on every machin…
t3dotgg Nov 9, 2026
2da34b6
chore(sync): merge pingdotgg/t3code main
Yarden-zamir Nov 9, 2026
8ab427c
fix(web): settings say custom instructions cover pull request descrip…
maria-rcks Nov 9, 2026
ad9e871
feat(web): send messages while attachments upload (#33092)
maria-rcks Nov 9, 2026
ade706a
Merge pull request #503 from Yarden-zamir/fork-sync/2026-11-09-2034
Yarden-zamir Nov 9, 2026
ff7aa5a
fix(mobile): Android PDF preview double-tap zoom and easier link taps…
juliusmarminge Nov 9, 2026
46181c5
chore(sync): merge pingdotgg/t3code main
Yarden-zamir Nov 9, 2026
72f0d5f
Merge pull request #504 from Yarden-zamir/fork-sync/2026-11-09-2234
Yarden-zamir Nov 9, 2026
3e4d449
chore(release): prepare v0.1.8
t3-code-bot[bot] Nov 9, 2026
dd0737d
feat: approve agent sign-ins with T3 Connect instead of a code (#33600)
juliusmarminge Nov 9, 2026
ee59ab2
fix(mobile): the composer and search bars move with their iOS columns…
juliusmarminge Nov 9, 2026
9110dc2
ci(mobile): build Android store releases on Blacksmith (#33565)
juliusmarminge Nov 9, 2026
45e0143
fix(web): thread title menu opens without a half-second wait on deskt…
t3dotgg Nov 9, 2026
b10cdde
fix(web): a new side chat focuses its message input (#32929)
darioielardi Nov 9, 2026
bbdc7dc
perf(opencode): bound usage reads to in-window messages (#33612)
Yash-Singh1 Nov 10, 2026
4b8044c
chore(sync): merge pingdotgg/t3code main
Yarden-zamir Nov 10, 2026
729147d
Merge pull request #505 from Yarden-zamir/fork-sync/2026-11-10-0035
Yarden-zamir Nov 10, 2026
d655e2c
chore(web): upgrade @pierre/diffs to 1.7 and use its built-in find (#…
juliusmarminge Nov 10, 2026
e6fa2e3
fix(web): expanding hidden diff context works after a diff refresh (#…
juliusmarminge Nov 10, 2026
9a0d027
feat: approve agent sign-ins on app.t3.codes with T3 Connect (#33632)
juliusmarminge Nov 10, 2026
53bcb9a
perf(cursor): improve account usage scanning (#33636)
Yash-Singh1 Nov 10, 2026
ad98905
fix(web): dropdown items over the desktop title bar respond to clicks…
screen-gd Nov 10, 2026
eb381a7
fix: hosted agent approval no longer fails with "Your session cannot …
juliusmarminge Nov 10, 2026
6f8c743
fix(web): new threads use the last used project
Yash-Singh1 Nov 10, 2026
f6a8a63
fix(mobile): distinguish local ACP instances by name (#32467)
shivamhwp Nov 10, 2026
83862af
ci(mobile): dev clients build on Blacksmith, and mobile:install start…
juliusmarminge Nov 10, 2026
9318fdb
fix(mobile): Android sidebar and composer transitions no longer reflo…
juliusmarminge Nov 10, 2026
9a3f8db
fix(server): price Antigravity Gemini Flash effort variants (#13809)
Ephraim-9 Nov 10, 2026
5e59ece
fix(mobile): Android feed stays at its end after the keyboard or side…
juliusmarminge Nov 10, 2026
12085f0
fix(server): Antigravity install no longer fails on a stale Windows C…
juliusmarminge Nov 10, 2026
b78c04d
ci(mobile): Android PR preview clients build on Blacksmith (#33618)
juliusmarminge Nov 10, 2026
046efd7
fix(web): scrolling pans expanded images and Mermaid diagrams (#26630)
shivamhwp Nov 10, 2026
5ae9578
fix(desktop): agents see visible:true while the desktop shows their t…
shivamhwp Nov 10, 2026
0c83258
fix(server): allow requested WebMCP writes (#26570)
shivamhwp Nov 10, 2026
e2aa223
fix(antigravity): resolve model names from usage metadata (#33661)
Yash-Singh1 Nov 10, 2026
16a6c1c
fix(web): show details for background commands (#33647)
Yash-Singh1 Nov 10, 2026
26f655a
chore(sync): merge pingdotgg/t3code main
Yarden-zamir Nov 10, 2026
c229123
chore(sync): measure agent context after the upstream merge
Yarden-zamir Nov 10, 2026
e3c92a0
Merge pull request #506 from Yarden-zamir/fork-sync/2026-11-10-0235
Yarden-zamir Nov 10, 2026
21300bb
chore(sync): merge Yarden-zamir/t3code main
TomCohenDev Nov 10, 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
5 changes: 3 additions & 2 deletions .agents/skills/test-t3-mobile/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ vp run mobile:install <ios|android> <device-id>

This installs the dev client for the checkout's native fingerprint: from the
device, the local cache, or the shared cache in seconds, building once only
when no machine has that fingerprint yet (5-15 minutes, logged to the file it
names). The device id may be omitted when exactly one device is booted. It
when no machine has that fingerprint yet: in the `Mobile Dev Client` workflow
on Blacksmith when HEAD is pushed and `gh` may dispatch it (the command waits),
otherwise here (5-15 minutes, logged to the file it names). The device id may be omitted when exactly one device is booted. It
prints one JSON line; on failure it names the next command. Mobile verification
includes that step unless the user prohibits it.

Expand Down
126 changes: 126 additions & 0 deletions .github/scripts/mobile-preview-comment.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// The Android half of the mobile preview comment. The android job writes the fingerprint and
// the update; the android-build job adds the client's install link a few minutes later. Each
// run merges what it knows into the state kept in the comment's first line.
const MARKER = "mobile-preview-android";
const PROJECT_ID = "d763fcb8-d37c-41ea-a773-b54a0ab4a454";
const APP_SCHEME = "t3-code";

function readState(body) {
const match = new RegExp(`^<!-- ${MARKER} (\\{.*\\}) -->`).exec(body ?? "");
if (!match) return null;
try {
const state = JSON.parse(match[1]);
const text = (value) => (typeof value === "string" ? value : "");
return {
fingerprint: text(state.fingerprint),
client: text(state.client),
group: text(state.group),
};
} catch {
return null;
}
}

/**
* New values win; a new fingerprint drops the old fingerprint's client. A build that finishes
* after the PR moved to another fingerprint changes nothing: its client cannot run the update
* the comment now shows.
*/
function mergeState(previous, next) {
const buildOnly = Boolean(next.client) && !next.group;
if (
buildOnly &&
previous?.fingerprint &&
next.fingerprint &&
previous.fingerprint !== next.fingerprint
) {
return previous;
}
const fingerprint = next.fingerprint || previous?.fingerprint || "";
const sameFingerprint = !previous?.fingerprint || previous.fingerprint === fingerprint;
return {
fingerprint,
client: next.client || (sameFingerprint ? (previous?.client ?? "") : ""),
group: next.group || previous?.group || "",
};
}

function render(state, runUrl) {
const qr = `https://qr.expo.dev/eas-update?appScheme=${APP_SCHEME}&projectId=${PROJECT_ID}&groupId=${state.group}`;
const rows = [
`<!-- ${MARKER} ${JSON.stringify(state)} -->`,
"🤖 Android preview",
"",
"&nbsp; | 🤖 Android",
"--- | ---",
`Fingerprint | ${state.fingerprint || "n/a"}`,
`Dev client | ${state.client ? `[Install](${state.client})` : `Building on Blacksmith ([run](${runUrl})); this comment gets the install link when it finishes`}`,
];
if (state.group) {
rows.push(
`Update Details | [Update Permalink](https://expo.dev/projects/${PROJECT_ID}/updates/${state.group})`,
`Update QR | <a href="${qr}"><img src="${qr}" width="250px" height="250px" /></a>`,
);
}
return rows.join("\n");
}

async function github(path, init = {}) {
const response = await fetch(`https://api.github.com${path}`, {
...init,
headers: {
authorization: `Bearer ${process.env.GH_TOKEN}`,
accept: "application/vnd.github+json",
"content-type": "application/json",
...init.headers,
},
});
if (!response.ok) {
throw new Error(
`GitHub ${init.method ?? "GET"} ${path}: ${response.status} ${await response.text()}`,
);
}
return response.json();
}

async function main() {
const repository = process.env.GITHUB_REPOSITORY;
const pr = process.env.PR;
if (!repository || !pr || !process.env.GH_TOKEN) {
throw new Error("GITHUB_REPOSITORY, PR and GH_TOKEN are required.");
}
const runUrl = `${process.env.GITHUB_SERVER_URL}/${repository}/actions/runs/${process.env.GITHUB_RUN_ID}`;
let existing = null;
for (let page = 1; existing === null; page++) {
const comments = await github(
`/repos/${repository}/issues/${pr}/comments?per_page=100&page=${page}`,
);
// Only this workflow's own comment: anyone can post the marker, and nobody else's is editable.
existing =
comments.find(
(comment) =>
comment.user?.login === "github-actions[bot]" && readState(comment.body) !== null,
) ?? null;
if (comments.length < 100) break;
}
const state = mergeState(readState(existing?.body), {
fingerprint: process.env.FINGERPRINT ?? "",
client: process.env.CLIENT ?? "",
group: process.env.GROUP ?? "",
});
const body = JSON.stringify({ body: render(state, runUrl) });
if (existing) {
await github(`/repos/${repository}/issues/comments/${existing.id}`, { method: "PATCH", body });
} else {
await github(`/repos/${repository}/issues/${pr}/comments`, { method: "POST", body });
}
}

if (require.main === module) {
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
}

module.exports = { mergeState, readState, render };
62 changes: 62 additions & 0 deletions .github/scripts/mobile-preview-comment.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const assert = require("node:assert/strict");
const test = require("node:test");

const { mergeState, readState, render } = require("./mobile-preview-comment.cjs");

const fingerprint = "c31608b37a8a2dca35ccee39cfd2ec7305042629";
const run = "https://github.com/pingdotgg/t3code/actions/runs/1";

test("the comment round-trips its state", () => {
const state = { fingerprint, client: "", group: "8e413a0a" };
const body = render(state, run);
assert.deepEqual(readState(body), state);
assert.match(
body,
/Building on Blacksmith \(\[run\]\(https:\/\/github\.com\/pingdotgg\/t3code\/actions\/runs\/1\)\)/,
);
assert.match(
body,
/qr\.expo\.dev\/eas-update\?appScheme=t3-code&projectId=[0-9a-f-]+&groupId=8e413a0a/,
);
assert.equal(readState("🚀 Expo continuous deployment is ready!"), null);
});

test("the build job adds the install link without losing the update", () => {
const first = mergeState(null, { fingerprint, client: "", group: "8e413a0a" });
const built = mergeState(first, { fingerprint, client: "https://expo.dev/a/b", group: "" });
assert.deepEqual(built, { fingerprint, client: "https://expo.dev/a/b", group: "8e413a0a" });
assert.match(render(built, run), /Dev client \| \[Install\]\(https:\/\/expo\.dev\/a\/b\)/);
});

test("a new fingerprint drops the old client; the same one keeps it", () => {
const built = { fingerprint, client: "https://expo.dev/a/b", group: "old" };
assert.deepEqual(mergeState(built, { fingerprint, client: "", group: "new" }), {
fingerprint,
client: "https://expo.dev/a/b",
group: "new",
});
assert.deepEqual(mergeState(built, { fingerprint: "f".repeat(40), client: "", group: "new" }), {
fingerprint: "f".repeat(40),
client: "",
group: "new",
});
});

test("a build that finishes after the PR moved to another fingerprint changes nothing", () => {
const current = { fingerprint: "f".repeat(40), client: "", group: "new" };
assert.deepEqual(
mergeState(current, { fingerprint, client: "https://expo.dev/a/b", group: "" }),
current,
);
});

test("state from the comment is always three strings", () => {
assert.deepEqual(
readState('<!-- mobile-preview-android {"fingerprint":5,"client":null} -->\nx'),
{
fingerprint: "",
client: "",
group: "",
},
);
});
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ jobs:
run: python3 -B .github/scripts/stage-preview-bundle.test.py

- name: Test release checks
run: node --test .github/scripts/check-nightly-release.test.cjs .github/scripts/vercel-deployment-output.test.cjs
run: node --test .github/scripts/check-nightly-release.test.cjs .github/scripts/vercel-deployment-output.test.cjs .github/scripts/mobile-preview-comment.test.cjs

- name: Finish installing browser secret helper build libraries
timeout-minutes: 5
Expand Down
Loading