Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ea15f17
fix(mobile): use native settings and snooze controls (#12512)
juliusmarminge Sep 18, 2026
d99bc5a
feat(web): sort pull requests by what is blocked on me (#12508)
flamboh Sep 18, 2026
8c3b5be
fix(mobile): prefer pull-to-refresh on list screens (#12515)
juliusmarminge Sep 18, 2026
de6a230
fix(acp): accept SDK elicitation requests (#11294)
shivamhwp Sep 19, 2026
bd0b9ed
fix(release): read relay configuration without loading deployment pro…
juliusmarminge Sep 19, 2026
4931d73
fix(ci): reconcile native change labels against pinned commits (#12517)
juliusmarminge Sep 19, 2026
e74c668
fix(release): strip Alchemy progress before parsing relay state (#12519)
juliusmarminge Sep 19, 2026
9cb586a
refactor: remove obsolete code (#9917)
t3dotgg Sep 19, 2026
cb3d95c
fix(server): release oversized pull request diff cache entries (#12523)
juliusmarminge Sep 19, 2026
8231193
feat(mobile): view and control agent devices (#12531)
juliusmarminge Sep 19, 2026
5378f87
fix(preview): recover host registration after request timeouts (#12535)
juliusmarminge Sep 19, 2026
0f1b572
fix(mobile): align built-in theme colors with desktop (#12534)
juliusmarminge Sep 19, 2026
82059df
feat(desktop): export main process telemetry over OTLP (#12520)
yordis Sep 19, 2026
efb9693
fix(codex): surface app permission requests as approvable (#7861)
Exotic209093 Sep 19, 2026
a8693eb
chore(desktop): leave main process metrics export off until a metric …
juliusmarminge Sep 19, 2026
803f94e
fix(release): drop placeholder allowBuilds entry that broke desktop b…
juliusmarminge Sep 19, 2026
29a23fd
fix(mobile): adapt workspace navigation and expand controls (#12551)
juliusmarminge Sep 19, 2026
7a326c2
chore(mobile): add dev client script with preview environment (#12558)
juliusmarminge Sep 19, 2026
408ff8a
fix: detect installed editors outside PATH (#12439)
Bil0000 Sep 19, 2026
dfbb11b
fix(web): show plain text in collapsed thought previews (#12377)
Bil0000 Sep 19, 2026
9accc56
fix(web): wrap long titles in confirmation dialogs (#12571)
saphid Sep 19, 2026
b44c1ce
fix(mobile): keep the Android composer placeholder on one line (#12605)
SunkenInTime Sep 19, 2026
d5accc8
Merge remote-tracking branch 'upstream/main' into yordis/chore-sync-u…
yordis Sep 19, 2026
75a1cb2
test(server): the fork migration guard builds its own in-memory database
yordis Sep 19, 2026
0842cdb
docs(fork): 0022 stops promising metrics the desktop never records
yordis Sep 19, 2026
6af6e9f
fix(web): drop the pull request composer target helper nobody calls
yordis Sep 19, 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
76 changes: 76 additions & 0 deletions .github/scripts/relay-state-output.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const assert = require("node:assert/strict");
const { spawnSync } = require("node:child_process");
const { mkdtempSync, readFileSync, rmSync, existsSync } = require("node:fs");
const { tmpdir } = require("node:os");
const { join } = require("node:path");
const { test } = require("node:test");

const workflow = readFileSync(join(__dirname, "../workflows/release.yml"), "utf8");
const step = workflow.match(
/- name: Read production relay tracing config\n[\s\S]*? run: \|\n((?: .*\n|\n)+)/,
);
assert.ok(step, "Could not find the relay state workflow step");
const script = step[1].replace(/^ /gm, "");
const config = {
clientTracingUrl: "https://example.invalid/traces",
clientTracingDataset: "fixture-dataset",
clientTracingToken: { __redacted__: "fixture-token" },
};
const json = JSON.stringify(config, null, 2);

function runStep(stdout, exitCode = 0) {
const runnerTemp = mkdtempSync(join(tmpdir(), "t3-relay-state-test-"));
try {
const result = spawnSync(
"bash",
["-c", 'npx() { printf "%s\\n" "$FIXTURE_STDOUT"; return "$FIXTURE_EXIT"; }\n' + script],
{
encoding: "utf8",
env: {
PATH: process.env.PATH,
RUNNER_TEMP: runnerTemp,
FIXTURE_STDOUT: stdout,
FIXTURE_EXIT: String(exitCode),
},
},
);
assert.ifError(result.error);
const envPath = join(runnerTemp, "relay-client-tracing.env");
return {
...result,
envFile: existsSync(envPath) ? readFileSync(envPath, "utf8") : undefined,
};
} finally {
rmSync(runnerTemp, { recursive: true, force: true });
}
}

for (const prefix of [
"",
"• Refreshing Cloudflare State Store credentials\n✓ Refreshing Cloudflare State Store credentials\n",
]) {
test(`extracts tracing config ${prefix ? "after progress output" : "from plain JSON"}`, () => {
const result = runStep(prefix + json);
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, "::add-mask::fixture-token\n");
assert.equal(
result.envFile,
"T3CODE_RELAY_CLIENT_OTLP_TRACES_URL=https://example.invalid/traces\n" +
"T3CODE_RELAY_CLIENT_OTLP_TRACES_DATASET=fixture-dataset\n" +
"T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=fixture-token\n",
);
});
}

for (const [name, stdout, exitCode] of [
["failed CLI even with valid JSON", json, 1],
["missing JSON", "Refreshing credentials...", 0],
["malformed JSON", "{not JSON", 0],
["missing token", JSON.stringify({ ...config, clientTracingToken: null }, null, 2), 0],
]) {
test(`rejects ${name} without writing config`, () => {
const result = runStep(stdout, exitCode);
assert.notEqual(result.status, 0);
assert.equal(result.envFile, undefined);
});
}
47 changes: 31 additions & 16 deletions .github/workflows/mobile-fingerprint-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,9 @@ name: Mobile Fingerprint Check
# 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
# Run even when a rebase or base change removes every native input from the
# diff, so the label can be cleared without installing Expo dependencies.
types: [opened, synchronize, reopened, edited]

concurrency:
group: mobile-fingerprint-check-${{ github.event.pull_request.number }}
Expand All @@ -44,12 +36,29 @@ jobs:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
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
# The merge ref can advance after the event is queued. Pin its commit
# and compare against its first parent, rather than the event's base.
ref: ${{ github.sha }}
fetch-depth: 2

- id: changes
name: Detect native fingerprint inputs
run: |
base_sha=$(git rev-parse HEAD^1)
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
paths=$(git diff --no-renames --name-only "$base_sha" HEAD -- \
apps/mobile/ packages/client-runtime/ packages/contracts/ packages/shared/ \
assets/ scripts/ patches/ package.json pnpm-lock.yaml pnpm-workspace.yaml \
.github/workflows/mobile-fingerprint-check.yml)
if [[ -n "$paths" ]]; then
echo "relevant=true" >> "$GITHUB_OUTPUT"
else
echo "relevant=false" >> "$GITHUB_OUTPUT"
echo "No native fingerprint inputs changed; clearing any stale native change label." >> "$GITHUB_STEP_SUMMARY"
fi

- name: Setup Vite+
if: steps.changes.outputs.relevant == 'true'
uses: voidzero-dev/setup-vp@250f29ce396baf5e8f24498e17c0dfdebabc26eb # v1.15.0
with:
node-version-file: package.json
Expand All @@ -59,13 +68,15 @@ jobs:
- --filter=@t3tools/mobile...

- name: Expose pnpm
if: steps.changes.outputs.relevant == 'true'
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
if: steps.changes.outputs.relevant == 'true'
working-directory: apps/mobile
run: |
mkdir -p "$RUNNER_TEMP/fp/head" "$RUNNER_TEMP/fp/base"
Expand All @@ -74,8 +85,11 @@ jobs:
done

- name: Fingerprint base
if: steps.changes.outputs.relevant == 'true'
env:
BASE_SHA: ${{ steps.changes.outputs.base_sha }}
run: |
git checkout --quiet "${{ github.event.pull_request.base.sha }}"
git checkout --quiet "$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.
Expand All @@ -87,6 +101,7 @@ jobs:

- id: compare
name: Compare fingerprints
if: steps.changes.outputs.relevant == 'true'
run: |
changed=""
{
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -324,15 +324,18 @@ jobs:
- --filter=t3code-relay...

# The deployed stack's outputs, read from Alchemy's state store without
# planning or applying. Redacted values are persisted as
# planning or applying. Select the backend explicitly to avoid loading
# the stack's unrelated provider credentials; suppress informational logs
# and strip the credential-refresh progress prefix before parsing JSON.
# Redacted values are persisted as
# {"__redacted__": "<value>"}; the token is masked before it is written.
- name: Read production relay tracing config
if: steps.creds.outputs.configured == 'true'
shell: bash
working-directory: infra/relay
run: |
set -euo pipefail
output="$(npx alchemy state read T3CodeRelay/prod/output --no-input)"
output="$(npx alchemy state read T3CodeRelay/prod/output --backend cloudflare --log-level error --no-input | sed -n '/^{/,$p')"
field() {
jq -er --arg key "$1" '.[$key] | if type == "object" then .__redacted__ else . end | select(. != null and . != "")' <<<"$output" \
|| { echo "Relay stack output is missing $1" >&2; exit 1; }
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/app/DesktopAppIdentity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const makeElectronAppLayer = (calls: ElectronAppCalls) =>
}),
setAppUserModelId: () => Effect.void,
getAppMetrics: Effect.succeed([]),
isDefaultProtocolClient: () => Effect.succeed(false),
setAsDefaultProtocolClient: () => Effect.succeed(true),
setDesktopName: () => Effect.void,
setDockIcon: (iconPath) =>
Expand Down
34 changes: 27 additions & 7 deletions apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { assert, describe, it } from "@effect/vitest";
import { ConnectionCatalogDocument } from "@t3tools/client-runtime/platform";
import { EnvironmentId, type PersistedSavedEnvironmentRecord } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as Encoding from "effect/Encoding";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Layer from "effect/Layer";
Expand All @@ -22,6 +23,11 @@ const textEncoder = new TextEncoder();
const decodeConnectionCatalog = Schema.decodeEffect(
Schema.fromJsonString(ConnectionCatalogDocument),
);
const encodeLegacySavedEnvironments = Schema.encodeEffect(
Schema.fromJsonString(
Schema.Struct({ version: Schema.Literal(1), records: Schema.Array(Schema.Unknown) }),
),
);
function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref<boolean> | null = null) {
return Layer.succeed(ElectronSafeStorage.ElectronSafeStorage, {
isEncryptionAvailable: Effect.succeed(available),
Expand Down Expand Up @@ -125,7 +131,8 @@ describe("DesktopConnectionCatalogStore", () => {
withStore(
Effect.gen(function* () {
const store = yield* DesktopConnectionCatalogStore.DesktopConnectionCatalogStore;
const savedEnvironments = yield* DesktopSavedEnvironments.DesktopSavedEnvironments;
const environment = yield* DesktopEnvironment.DesktopEnvironment;
const fileSystem = yield* FileSystem.FileSystem;
const records: readonly PersistedSavedEnvironmentRecord[] = [
{
environmentId: EnvironmentId.make("relay-environment"),
Expand Down Expand Up @@ -159,11 +166,21 @@ describe("DesktopConnectionCatalogStore", () => {
lastConnectedAt: null,
},
];
yield* savedEnvironments.setRegistry(records);
assert.isTrue(
yield* savedEnvironments.setSecret({
environmentId: EnvironmentId.make("bearer-environment"),
secret: "legacy-token",
yield* fileSystem.makeDirectory(environment.stateDir, { recursive: true });
yield* fileSystem.writeFileString(
environment.savedEnvironmentRegistryPath,
yield* encodeLegacySavedEnvironments({
version: 1,
records: records.map((record) =>
record.environmentId === "bearer-environment"
? {
...record,
encryptedBearerToken: Encoding.encodeBase64(
textEncoder.encode("encrypted:legacy-token"),
),
}
: record,
),
}),
);

Expand Down Expand Up @@ -218,7 +235,10 @@ describe("DesktopConnectionCatalogStore", () => {
assert.equal(catalog.credentials[0].credential.token, "legacy-token");
}

yield* savedEnvironments.setRegistry([]);
yield* fileSystem.writeFileString(
environment.savedEnvironmentRegistryPath,
'{"version":1,"records":[]}',
);
assert.deepEqual(yield* store.get, migrated);
}),
),
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/app/DesktopLifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ function makeElectronAppLayer(
setAboutPanelOptions: () => Effect.void,
setAppUserModelId: () => Effect.void,
getAppMetrics: Effect.succeed([]),
isDefaultProtocolClient: () => Effect.succeed(false),
setAsDefaultProtocolClient: () => Effect.succeed(true),
setDesktopName: () => Effect.void,
setDockIcon: () => Effect.void,
Expand Down
Loading
Loading