Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
11e91f1
feat(mobile): capture Live Activities and agent notifications in show…
juliusmarminge Sep 23, 2026
68fb7f4
feat(mobile): manage environment and provider updates (#13302)
juliusmarminge Sep 23, 2026
e407f9b
fix(ci): shard release tests like pull request CI (#13321)
juliusmarminge Sep 23, 2026
effaab9
fix(web): show previous worktree branch on second line (#13314)
Yash-Singh1 Sep 23, 2026
d4cd7d5
fix(providers): restore compatibility ranges for every harness (#13328)
juliusmarminge Sep 24, 2026
894d334
fix(preview): use the visible browser for new agent sessions (#13064)
Bil0000 Sep 24, 2026
e4eb997
fix(server): stop replaying old agent alerts on restart (#13340)
juliusmarminge Sep 24, 2026
c0912de
fix(web): use a brain icon for the effort dropdown (#13309)
t3-code[bot] Sep 24, 2026
6b4b190
fix(desktop): SnapShot shortcut helper no longer adds a Dock icon on …
Gigioxx Sep 24, 2026
9030a60
fix(web): composer chip rings no longer clip at the editor edge (#13301)
flamboh Sep 24, 2026
21e2b7d
fix(web): switches announce their real state to screen readers (#11580)
Leos-Khai Sep 24, 2026
f1add18
fix(shared): preserve final quoted empty CSV records (#11425)
Lucenx9 Sep 24, 2026
78af372
feat(web): add an interactive 3D device workspace (#12787)
juliusmarminge Sep 24, 2026
9383f4a
chore: add scratchyone to vouched list (#13353)
t3-code[bot] Sep 24, 2026
e67abcf
feat(observability): honor the OpenTelemetry kill switch (#13355)
juliusmarminge Sep 24, 2026
bf899cf
fix(server): Codex /goal clear removes the persisted goal
macodev00 Sep 24, 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
1 change: 1 addition & 0 deletions .github/VOUCHED.td
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ github:realAhmedRoach
github:Rishet11
github:ryanrhughes
github:saphid
github:scratchyone
github:sethwebster
github:shiroyasha9
github:shivamhwp
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/mobile-eas-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,19 @@ jobs:
working-directory: apps/mobile
env:
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
run: eas env:pull preview --non-interactive
run: |
eas env:pull preview --non-interactive
# EAS Update disables dotenv loading. Keep the downloaded native config
# in its process environment so Android build and OTA fingerprints match.
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from "node:fs";
import { parseEnv } from "node:util";
const env = parseEnv(readFileSync(".env.local", "utf8"));
const googleServicesFile = env.T3CODE_ANDROID_GOOGLE_SERVICES_FILE;
if (googleServicesFile) {
appendFileSync(process.env.GITHUB_ENV, `T3CODE_ANDROID_GOOGLE_SERVICES_FILE=${googleServicesFile}\n`);
}
NODE

- name: Deploy with fingerprint check
if: steps.expo-token.outputs.present == 'true'
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/mobile-showcase-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ jobs:
echo "$vp_pnpm_bin" >> "$GITHUB_PATH"
"$vp_pnpm_bin/pnpm" --version

- name: Install AXe
# Locks the simulator and answers the notification prompt for the
# agent-activity scene.
# Homebrew refuses third-party tap formulae until they are trusted;
# older releases have no trust command and install them as is.
run: |
brew tap cameroncooke/axe
if brew commands | grep -qx trust; then brew trust --formula cameroncooke/axe/axe; fi
brew install cameroncooke/axe/axe
axe --version

- name: Capture iOS showcase
run: pnpm screenshots:mobile --platform ios --appearance "${{ inputs.appearance }}" --theme "${{ inputs.theme }}"

Expand Down
71 changes: 68 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,76 @@ jobs:
- name: Typecheck
run: vp run typecheck

# Keep tests on their own runners, using the same package split and server
# shards as CI, so the release check job does not spend its budget on tests.
test:
name: Release tests
needs: [preflight]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
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: true

- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

- uses: ./.github/actions/setup-apt-mirrors

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config
run: |
sudo sed -i 's|http://|https://|g' /etc/apt/blacksmith-ubuntu-mirrors.txt /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config build-essential

- name: Test
run: vp run --parallel --concurrency-limit 4 --filter '!t3' --filter '!@t3tools/monorepo' test

test_server:
name: Release server tests ${{ matrix.shard }}
needs: [preflight]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
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: true

# No Electron setup here: `t3` (apps/server) has no Electron dependency
# and none of its tests touch the runtime. Only the non-server `test`
# job, which covers @t3tools/desktop, needs the download.
- name: Test
run: vp run test
run: vp run --filter t3 test --shard ${{ matrix.shard }}/${{ strategy.job-total }}

relay_public_config:
name: Resolve T3 Connect public config
Expand Down Expand Up @@ -630,13 +693,15 @@ jobs:
preflight,
relay_public_config,
quality,
test,
test_server,
desktop_mac_arm64,
desktop_linux_x64,
desktop_linux_arm64,
desktop_win_x64,
desktop_win_arm64,
]
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.quality.result == 'success' && needs.desktop_mac_arm64.result == 'success' && needs.desktop_linux_x64.result == 'success' && needs.desktop_linux_arm64.result == 'success' && needs.desktop_win_x64.result == 'success' && needs.desktop_win_arm64.result == 'success' }}
if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.quality.result == 'success' && needs.test.result == 'success' && needs.test_server.result == 'success' && needs.desktop_mac_arm64.result == 'success' && needs.desktop_linux_x64.result == 'success' && needs.desktop_linux_arm64.result == 'success' && needs.desktop_win_x64.result == 'success' && needs.desktop_win_arm64.result == 'success' }}
runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 15
permissions:
Expand Down
64 changes: 64 additions & 0 deletions apps/desktop/src/app/DesktopObservability.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, describe, it } from "@effect/vitest";
import * as ConfigProvider from "effect/ConfigProvider";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
Expand Down Expand Up @@ -427,6 +428,36 @@ describe("DesktopObservability", () => {
);
});

it.effect("exports kill switch warnings through the configured logger", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-observability-test-",
});
const environmentLayer = makeEnvironmentLayer(baseDir, true, {
T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.void.pipe(
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.include(requests[0]?.body ?? "", "OTEL_SDK_DISABLED=1 was read as false");
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "1" } })),
),
),
);
});

it.effect("reads every signal endpoint from Settings when the environment names none", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
Expand Down Expand Up @@ -491,4 +522,37 @@ describe("DesktopObservability", () => {
Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))),
);
});

it.effect("stops every export when the OpenTelemetry SDK is disabled", () => {
const requests: Array<ExportedRequest> = [];
return Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-observability-test-",
});
const environmentLayer = makeEnvironmentLayer(baseDir);
yield* writeObservabilitySettings(environmentLayer, {
otlpTracesUrl: "https://settings.example.com/v1/traces",
otlpLogsUrl: "https://settings.example.com/v1/logs",
});

yield* Effect.scoped(
Effect.logInfo("desktop log stays local when disabled").pipe(
Effect.withSpan("desktop-disabled-test"),
Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))),
),
);

assert.lengthOf(requests, 0);
}).pipe(
Effect.scoped,
Effect.provide(
Layer.mergeAll(
NodeServices.layer,
collectorLayer(requests),
ConfigProvider.layer(ConfigProvider.fromEnv({ env: { OTEL_SDK_DISABLED: "true" } })),
),
),
);
});
});
14 changes: 13 additions & 1 deletion apps/desktop/src/app/DesktopObservability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
makeTraceSink,
otlpSerializationLayer,
} from "@t3tools/shared/observability";
import * as OtelEnvironment from "@t3tools/shared/otelEnvironment";
import {
parsePersistedServerObservabilitySettings,
type PersistedServerObservabilitySettings,
Expand Down Expand Up @@ -353,12 +354,18 @@ const readPersistedObservabilitySettings: Effect.Effect<
* resolve traces against one revision of the file and logs against another.
*/
const resolveOtlpEndpoints = Effect.gen(function* () {
const otel = yield* OtelEnvironment.load;
if (otel.disabled) {
return { traces: undefined, metrics: undefined, logs: undefined, warnings: otel.warnings };
}

const environment = yield* DesktopEnvironment.DesktopEnvironment;
const persisted = yield* readPersistedObservabilitySettings;
return {
traces: Option.getOrUndefined(environment.otlpTracesUrl) ?? persisted.otlpTracesUrl,
metrics: Option.getOrUndefined(environment.otlpMetricsUrl) ?? persisted.otlpMetricsUrl,
logs: Option.getOrUndefined(environment.otlpLogsUrl) ?? persisted.otlpLogsUrl,
warnings: otel.warnings,
};
});

Expand Down Expand Up @@ -671,7 +678,12 @@ const telemetryLayer = Layer.unwrap(
// resource,
// }).pipe(Layer.provide(serializationLayer));

return Layer.mergeAll(loggerLayer, tracerLayer);
// Logged once the loggers above are installed, so the warnings use them.
const otelWarningsLayer = Layer.effectDiscard(
Effect.forEach(endpoints.warnings, (warning) => Effect.logWarning(warning)),
);

return otelWarningsLayer.pipe(Layer.provideMerge(Layer.mergeAll(loggerLayer, tracerLayer)));
}),
);

Expand Down
43 changes: 43 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,49 @@ describe("DesktopBackendConfiguration", () => {
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("resolveWsl carries the kill switch into the distro", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
const baseDir = yield* fileSystem.makeTempDirectoryScoped({
prefix: "t3-desktop-backend-config-test-",
});

const previousWslEnv = process.env.WSLENV;
const previousDisabled = process.env.OTEL_SDK_DISABLED;
try {
delete process.env.WSLENV;
process.env.OTEL_SDK_DISABLED = "true";

yield* Effect.gen(function* () {
const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration;
const config = yield* configuration.resolveWsl({ port: 5050, distro: null });

assert.equal(config.env.OTEL_SDK_DISABLED, "true");
assert.include((config.env.WSLENV ?? "").split(":"), "OTEL_SDK_DISABLED");
}).pipe(
Effect.provide(
DesktopBackendConfiguration.layer.pipe(
Layer.provideMerge(serverExposureLayer),
Layer.provideMerge(DesktopAppSettings.layerTest()),
Layer.provideMerge(DesktopWslServerTree.layerTest()),
Layer.provideMerge(
DesktopWslEnvironment.layerTest({
isAvailable: true,
windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"),
getDistroIp: () => Option.some("172.27.0.99"),
}),
),
Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })),
),
),
);
} finally {
restoreEnv("WSLENV", previousWslEnv);
restoreEnv("OTEL_SDK_DISABLED", previousDisabled);
}
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("resolveWsl preserves existing WSLENV entries when forwarding backend secrets", () =>
Effect.gen(function* () {
const fileSystem = yield* FileSystem.FileSystem;
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src/backend/DesktopBackendConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const DESKTOP_BACKEND_ENV_NAMES = [
const WSL_FORWARDED_ENV_NAMES = [
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
// Otherwise the WSL server keeps exporting to endpoints from the bootstrap.
"T3CODE_OTEL_SDK_DISABLED",
"OTEL_SDK_DISABLED",
"T3CODE_OTLP_HEADERS",
"T3CODE_OTLP_PROTOCOL",
] as const;
Expand Down
11 changes: 9 additions & 2 deletions apps/desktop/src/electron/ElectronProtocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe("ElectronProtocol", () => {
);
assert.include(
response.headers.get("content-security-policy") ?? "",
"connect-src 'self' http: https: ws: wss:",
"connect-src 'self' blob: http: https: ws: wss:",
);
assert.include(
response.headers.get("content-security-policy") ?? "",
Expand Down Expand Up @@ -255,7 +255,14 @@ describe("ElectronProtocol", () => {
"https://clerk.t3.codes",
"https://challenges.cloudflare.com",
]);
assert.deepEqual(directives["connect-src"], ["'self'", "http:", "https:", "ws:", "wss:"]);
assert.deepEqual(directives["connect-src"], [
"'self'",
"blob:",
"http:",
"https:",
"ws:",
"wss:",
]);
assert.deepEqual(directives["img-src"], [
"'self'",
"t3code:",
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/electron/ElectronProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrat
// the build-configured Clerk, relay, and OTLP endpoints. Those environment
// origins are not known when this response policy is created, so restrict
// connections by the network schemes the client supports instead of by host.
const connectSources = ["'self'", "http:", "https:", "ws:", "wss:"];
// GLTFLoader fetches embedded textures through blob URLs after parsing the model.
const connectSources = ["'self'", "blob:", "http:", "https:", "ws:", "wss:"];

return [
"default-src 'self'",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const MENU_ACTION_CHANNEL = "desktop:menu-action";
export const PASTE_AS_TEXT_CHANNEL = "desktop:paste-as-text";
export const SNAP_SHOT_EVENT_CHANNEL = "desktop:snap-shot-event";
export const QUIT_SHORTCUT_CHANNEL = "desktop:quit-shortcut";
export const TRACKPAD_SCROLL_END_CHANNEL = "desktop:trackpad-scroll-end";
export const GET_WINDOW_FULLSCREEN_STATE_CHANNEL = "desktop:get-window-fullscreen-state";
export const WINDOW_FULLSCREEN_STATE_CHANNEL = "desktop:window-fullscreen-state";
export const DESKTOP_APP_ACTIVATION_READY_CHANNEL = "desktop:app-activation-ready";
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ contextBridge.exposeInMainWorld("desktopBridge", {
ipcRenderer.on(IpcChannels.SET_NOTIFICATION_BADGE_CHANNEL, handler);
return () => ipcRenderer.removeListener(IpcChannels.SET_NOTIFICATION_BADGE_CHANNEL, handler);
},
onTrackpadScrollEnd: (listener) => {
const handler = () => listener();
ipcRenderer.on(IpcChannels.TRACKPAD_SCROLL_END_CHANNEL, handler);
return () => ipcRenderer.removeListener(IpcChannels.TRACKPAD_SCROLL_END_CHANNEL, handler);
},
getSystemLocale: () => {
const result = ipcRenderer.sendSync(IpcChannels.GET_SYSTEM_LOCALE_CHANNEL);
return typeof result === "string" ? result : null;
Expand Down
4 changes: 4 additions & 0 deletions apps/desktop/src/snapShot/MacModifierPairShortcutProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ const MAC_MODIFIER_PAIR_DEVICE_MASKS: Record<SnapShotModifier, readonly [number,
meta: [0x8, 0x10],
};

// The CoreGraphics query connects osascript to the window server, which registers it as a
// foreground app attributed to T3 Code. Go background-only first so it never gets a Dock tile.
const POLLER_SCRIPT = `
ObjC.import("AppKit");
$.NSApplication.sharedApplication.setActivationPolicy($.NSApplicationActivationPolicyProhibited);
ObjC.import("CoreGraphics");
ObjC.import("unistd");
function run(argv) {
Expand Down
Loading
Loading