Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
9b1dee7
Merge remote-tracking branch 'upstream/main' into yordis/chore-sync-u…
yordis Nov 10, 2026
770ae3c
ci: pin GitHub Actions to commit SHAs
yordis Nov 10, 2026
bf4eb25
ci: disable android and ios production builds
yordis Nov 10, 2026
b0782c9
fix(server): record the merged agent context budget
yordis 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 @@ -36,8 +36,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
Loading