Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
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
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
ff7aa5a
fix(mobile): Android PDF preview double-tap zoom and easier link taps…
juliusmarminge 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
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
f728112
fix(ci): store-build checkouts drop credentials without submodule cle…
juliusmarminge Nov 10, 2026
8226715
fix(web): "Use T3 Code in another app" opens the Grok Bot app (#33663)
juliusmarminge Nov 10, 2026
73fb49d
ci(mobile): keep the Android store AAB as a workflow artifact (#33674)
juliusmarminge Nov 10, 2026
331889a
fix(mobile): hide compose in the detail rail while starting a task (#…
juliusmarminge Nov 10, 2026
d1909d6
chore(mobile): bump app version to 2.3.0
t3-code-bot[bot] Nov 10, 2026
20bb685
ci(mobile): version bump commits no longer credit Codex (#33689)
juliusmarminge Nov 10, 2026
f6a32db
fix(server): stop PR watch flapping while workflows await approval (#…
shivamhwp Nov 10, 2026
e89abad
fix(server): show idle paragraphs before the assistant turn ends (#28…
shivamhwp Nov 10, 2026
6c9adfb
fix(mobile): fit usage view selector beside the date range at large t…
PixPMusic Nov 10, 2026
934c7f8
fix(web): center short PR titles in thread details (#33724)
Yash-Singh1 Nov 10, 2026
4c36119
fix(server): Claude threads can allow an MCP app for the session (#33…
t3dotgg Nov 10, 2026
25da7eb
fix(web): reclaim notice says the machine will be offline (#33730)
shivamhwp Nov 10, 2026
577c524
fix(server): detaching a session no longer strands its pending approv…
juliusmarminge Nov 10, 2026
eff4566
fix(opencode): long command turns no longer fail when the command req…
juliusmarminge Nov 10, 2026
4168c14
fix(server): a queued message that names no model keeps the model pic…
juliusmarminge Nov 10, 2026
4d9caf1
fix(ssh): tunnel readiness no longer counts ssh's connection time (#3…
juliusmarminge Nov 10, 2026
4f57044
fix(server): preview_evaluate accepts top-level await (#31925)
juliusmarminge Nov 10, 2026
5636b56
fix(server): quitting no longer restarts the relay client mid-shutdow…
juliusmarminge Nov 10, 2026
9e12eca
fix(shared): relative Windows file links open the file (#33643)
juliusmarminge Nov 10, 2026
8d54c8b
fix(server): a steer that restarts a completion wake no longer strand…
juliusmarminge Nov 10, 2026
5dcf69e
fix(server): provider status checks no longer make mise download miss…
juliusmarminge Nov 10, 2026
b37c7f6
fix(forgejo): find merged pull requests whose head branch was deleted…
juliusmarminge Nov 10, 2026
65ffcbf
perf(shared): speed up escaped Markdown in long PR descriptions (#33438)
AksharP5 Nov 10, 2026
cec8d73
fix(server): a background agent tab survives a desktop window reload …
juliusmarminge Nov 10, 2026
ad91636
fix(server): Commit, push & PR no longer pushes the default branch in…
juliusmarminge Nov 10, 2026
d47591f
fix(acp): OMP threads show html_render pages (#33677)
juliusmarminge Nov 10, 2026
56064fc
fix(ssh): Intel Mac hosts no longer try to download a missing t3 arch…
juliusmarminge Nov 10, 2026
a22068f
fix(server): opening the branch menu no longer wipes PR status (#33263)
juliusmarminge Nov 10, 2026
633438d
fix(server): failed updates report download diagnostics (#33239)
tanav29 Nov 10, 2026
e4d86ab
fix(web): remote Lineage rows keep their title when the environment n…
f4llenz Nov 10, 2026
580503e
fix(web): custom snooze footer stays inside the dialog (#33759)
juliusmarminge Nov 10, 2026
2922e8f
fix(desktop): right-clicking a mailto link offers Copy Email Address …
juliusmarminge Nov 10, 2026
7171fdd
fix(acp-registry): commands past the 128th are no longer dropped (#33…
juliusmarminge Nov 10, 2026
f8fbc19
fix(server): one finished turn looks up its branch's pull request onc…
juliusmarminge Nov 10, 2026
ca796d5
fix(mobile): Android Files sheet scrolls its tree instead of dragging…
RadekManak Nov 10, 2026
b48b8ea
fix(web): prompt suggestions show after a steered turn (#33706)
thelogicmatrix Nov 10, 2026
0ca73a1
fix(server): usage prices Claude Mythos 5.2 from its Bedrock rate (#3…
juliusmarminge Nov 10, 2026
bcee0f5
fix(server): Claude turns whose CLI exits before its first frame fail…
juliusmarminge Nov 10, 2026
fa2f4ff
fix(web): panel resize handles follow touch drags (#33066)
juliusmarminge Nov 10, 2026
feb9c6b
fix(server): signed-out Claude instances with their own config dir na…
juliusmarminge Nov 10, 2026
61cb845
perf(server): opening a long thread reads from its latest segments, n…
t3dotgg Nov 10, 2026
d1aeed2
fix(server): repos without a remote no longer record failed spans on …
ferhtgoldaraz Nov 10, 2026
5a9866d
fix(mobile): missing files in previews name the checked path instead …
juliusmarminge Nov 10, 2026
f12bb62
fix(server): a restart that misses its turn starts as a new turn (#32…
juliusmarminge Nov 10, 2026
4c924ff
fix(desktop): new browser tabs no longer lose waits, clicks and snaps…
juliusmarminge Nov 10, 2026
7114615
Merge upstream pingdotgg/t3code main through 4c924ff4e6 into fork
eyeveil Nov 10, 2026
8932518
fix(client-runtime): environment requests keep the connection label f…
eyeveil 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 });
Comment on lines +106 to +113

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Preview comments lose newer links

On consecutive pushes with the same native fingerprint, an earlier Android build can finish while the newer update job writes its comment. Both writers read the old body and replace the whole comment. The last write can restore the older update QR or erase the finished client's install link. Serialize comment writes per PR so each merge reads the previous writer's result.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/scripts/mobile-preview-comment.cjs
Line: 106-113

Comment:
**Preview comments lose newer links**

On consecutive pushes with the same native fingerprint, an earlier Android build can finish while the newer update job writes its comment. Both writers read the old body and replace the whole comment. The last write can restore the older update QR or erase the finished client's install link. Serialize comment writes per PR so each merge reads the previous writer's result.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Written by claude-opus-5-5 on behalf of eyeveil

Not fixing this here. .github/scripts/mobile-preview-comment.cjs is identical to upstream 4c924ff4e6d, and on this fork the jobs that write that comment never run. Same-repo PR previews use Mobile EAS Preview, which is disabled on issuelt/t3code. Mobile EAS Preview (Fork) runs only for labeled PRs from other repositories. If the race matters, the fix belongs upstream, where those workflows run.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point that this is an upstream-only sync and a fork-specific fix would create divergence. One qualification: the workflow definitions still include comment writers for labeled external PRs in mobile-eas-preview-fork.yml; its android-build can outlive the preview job, so a later labeled run could overlap it. And mobile-eas-preview.yml is not disabled in its YAML—the job condition permits same-repo labeled PRs (though repository settings may disable it).

So I agree this needn’t be fixed in this sync, but I wouldn’t conclude the race is impossible for the fork workflow. It’s best treated as an upstream-owned issue and addressed there, or tracked separately if that fork workflow is used.

} 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