Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6382268
fix(web): let paste expand a resting composer (#9498)
juliusmarminge Sep 4, 2026
522ebe6
fix(web): keep the composer open while selecting timeline text (#9499)
juliusmarminge Sep 4, 2026
c0ebc88
fix(web): return focus to the composer after closing a media preview …
juliusmarminge Sep 4, 2026
9c9ae3d
fix(server): keep events during thread subscription startup (#9521)
t3dotgg Sep 4, 2026
44701ef
chore: forward issue/PR/discussion events to Cursor hygiene (#9518)
juliusmarminge Sep 4, 2026
9d28c21
fix(auth): keep pairing credentials out of access read models (#9523)
t3dotgg Sep 4, 2026
e3723e0
chore: drop comment events from Cursor hygiene forwarder (#9527)
juliusmarminge Sep 4, 2026
d76b24d
feat(codex): support async questions (#9512)
t3dotgg Sep 4, 2026
0a0b6be
fix(web): keep right panel controls clickable (#9517)
maria-rcks Sep 4, 2026
19d8ab2
feat(usage): show Codex and Claude subscription limits on a Limits ta…
juliusmarminge Sep 4, 2026
343db2c
feat(web): reorganize settings pages (#9354)
maria-rcks Sep 4, 2026
2b96220
fix(server): settle branch threads immediately on pull request merge …
maria-rcks Sep 4, 2026
b908980
fix(server): back off relay client restarts after rapid exits (#8788)
derektrimm Sep 4, 2026
75ab5ab
fix(codex): accept rate limit errors on thread resume (#8897)
nateEc Sep 4, 2026
6319a97
fix(desktop): preview CDP sessions no longer hard-crash the app (#9068)
sethwebster Sep 4, 2026
f54ab90
Fix worktree removal timing out on large install trees (#3902)
jakeleventhal Sep 4, 2026
0ba06a1
fix(web): settle the resting composer layout with a pixel of slack (#…
matheustimbo Sep 4, 2026
07c4ab5
fix(web): keep automatic project icons consistent (#9535)
t3dotgg Sep 4, 2026
dddc0bd
fix(server): include SQLite conditions in persistence errors
t3dotgg Sep 4, 2026
6f40537
fix(dev): keep shared dev reloads and hot updates working (#9543)
t3dotgg Sep 4, 2026
c5ba51d
feat(providers): add context compaction command (#9293)
maria-rcks Sep 4, 2026
5989de4
fix(mobile): keep store screenshots free of system banners and show d…
juliusmarminge Sep 4, 2026
54aef6f
fix(web): restore composer controls as space becomes available (#9539)
juliusmarminge Sep 4, 2026
3c3e05c
fix(web): measure collapsed model labels at their visible width (#9540)
juliusmarminge Sep 4, 2026
f239b77
fix(web): close composer menus when their controls hide (#9541)
juliusmarminge Sep 4, 2026
3e2c1a6
fix(web): thread error banner no longer shifts the chat (#9473)
flamboh Sep 4, 2026
617edab
fix(server): reveal normalized paths in File Explorer (#9551)
juliusmarminge Sep 4, 2026
232de5e
feat(marketing): fresh screenshot and floating marks on the homepage …
t3dotgg Sep 4, 2026
1641b4a
feat(usage): redeem Codex reset credits from the Limits tab (#9534)
juliusmarminge Sep 4, 2026
4e54731
fix(server): find newly opened pull requests after agent turns (#9125)
t3dotgg Sep 4, 2026
f96a220
ci: add on-demand Windows test workflow (#9538)
juliusmarminge Sep 4, 2026
710f6dc
fix(web): simplify expanded tool details (#9549)
maria-rcks Sep 4, 2026
4febcc9
sync(upstream): wave 4 settings reorg, usage limits, and compaction
cursoragent Sep 4, 2026
14146a1
fix(server): map usage-limit windows into the rate-limit panel
cursoragent Sep 4, 2026
3695dbc
fix(sync): restore attachment inline metadata and CI test expectations
cursoragent Sep 4, 2026
2591dc7
chore: format wave 4 files for vp check
cursoragent Sep 4, 2026
813f008
fix(web): drop unused react-hooks eslint-disable comments
cursoragent Sep 4, 2026
495a78a
fix(sync): unblock CI after upstream merge leftovers
cursoragent Sep 4, 2026
200a8db
Merge remote-tracking branch 'origin/codex/sync-upstream-20260904-wav…
cursoragent Sep 4, 2026
8e22b11
chore(sync): record current wave 3 ancestry
aaditagrawal Sep 5, 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
36 changes: 36 additions & 0 deletions .github/workflows/cursor-hygiene-webhook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Forward to Cursor hygiene

on:
push:
branches: [main]
pull_request:
types: [opened, reopened, ready_for_review]
issues:
types: [opened, closed, reopened]
discussion:
types: [created, closed, reopened]

permissions:
contents: read

jobs:
forward:
name: POST to Cursor
runs-on: ubuntu-24.04
steps:
- name: POST to Cursor
env:
URL: ${{ secrets.CURSOR_T3CODE_WEBHOOK_URL }}
AUTH: ${{ secrets.CURSOR_T3CODE_WEBHOOK_AUTH }}
run: |
set -euo pipefail
if [ -z "${URL:-}" ] || [ -z "${AUTH:-}" ]; then
echo "Missing CURSOR_T3CODE_WEBHOOK_URL or CURSOR_T3CODE_WEBHOOK_AUTH — skipping."
exit 0
fi
curl -fsS --max-time 60 -X POST "$URL" \
-H "Authorization: $AUTH" \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: ${{ github.event_name }}" \
-H "X-GitHub-Delivery: ${{ github.run_id }}-${{ github.run_attempt }}" \
--data-binary @"${{ github.event_path }}"
81 changes: 81 additions & 0 deletions .github/workflows/windows-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# On-demand Windows test lane. Manual only: nothing in the suite passes on
# Windows yet, so this exists to give contributors (and agents) a cloud Windows
# box to iterate against. Once the suite is green here, fold it into ci.yml.
#
# gh workflow run windows-tests.yml --ref <branch> -f package=packages/shared
# gh workflow run windows-tests.yml --ref <branch> -f package=apps/server \
# -f files="src/process/externalLauncher.test.ts src/cli/theme.test.ts"
# gh run watch && gh run view --log-failed
name: Windows Tests

on:
workflow_dispatch:
inputs:
package:
description: "Workspace directory to test, e.g. apps/server or packages/shared. Empty runs every package except apps/server."
type: string
default: ""
files:
description: "Space-separated test files relative to the package directory. Empty runs the package's whole suite. Requires package."
type: string
default: ""

permissions:
contents: read

jobs:
test:
name: Test (${{ inputs.package || 'all non-server' }})
runs-on: blacksmith-8vcpu-windows-2025
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
with:
sparse-checkout: |
/*
!/.repos/
sparse-checkout-cone-mode: false

# setup-vp's own cache restores a Linux-shaped store on Windows, which is
# slower than no cache (see #7975). Cache pnpm's Windows store directly.
- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
cache: false
run-install: false

- name: Resolve package cache path
id: package_cache_path
shell: pwsh
run: '"path=$(vp pm cache dir)" >> $env:GITHUB_OUTPUT'

- name: Cache packages
uses: actions/cache@v6
with:
path: ${{ steps.package_cache_path.outputs.path }}
key: windows-tests-packages-v1-${{ hashFiles('pnpm-lock.yaml') }}

- name: Install
run: vp install

- name: Ensure Electron runtime is installed
if: inputs.package == '' || inputs.package == 'apps/desktop'
run: vp run --filter "@t3tools/desktop" ensure:electron

# `vp run ... test -- <files>` does not forward positional args to vitest,
# so file-scoped runs call `vp test run` inside the package instead.
- name: Test
shell: pwsh
run: |
$package = '${{ inputs.package }}'
$files = '${{ inputs.files }}'
if ($package -eq '') {
vp run --parallel --concurrency-limit 4 --filter '!t3' --filter '!@t3tools/monorepo' test
} elseif ($files -eq '') {
vp run --filter "./$package" test
} else {
Set-Location $package
vp test run $files.Split(' ')
}
4 changes: 3 additions & 1 deletion apps/desktop/scripts/ensure-electron-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as NodeFS from "node:fs";
import * as NodeModule from "node:module";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import * as NodeURL from "node:url";
import * as NodeChildProcess from "node:child_process";

const require = NodeModule.createRequire(import.meta.url);
Expand Down Expand Up @@ -176,7 +177,8 @@ export function ensureElectronRuntime() {
return electronPath;
}

if (import.meta.url === `file://${process.argv[1]}`) {
// `file://${argv[1]}` never matches on Windows (drive letters need `file:///C:/`).
if (process.argv[1] && NodeURL.pathToFileURL(process.argv[1]).href === import.meta.url) {
const electronPath = ensureElectronRuntime();
process.stdout.write(`${electronPath}\n`);
}
61 changes: 61 additions & 0 deletions apps/desktop/src/preview/Manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,67 @@ describe("PreviewManager", () => {
),
);

effectIt.effect("detaches through the pinned debugger after the webview is destroyed", () =>
withManager((manager) =>
Effect.gen(function* () {
// Real Electron throws on any `wc.debugger` access once the
// WebContents is destroyed, so cleanup must go through the debugger
// reference captured at attach time (electron/electron#53376).
let destroyed = false;
let attached = false;
const debuggerOff = vi.fn();
const debuggerDetach = vi.fn(() => {
attached = false;
});
const wcDebugger = {
isAttached: () => attached,
attach: vi.fn(() => {
attached = true;
}),
detach: debuggerDetach,
sendCommand: vi.fn(async () => undefined),
on: vi.fn(),
off: debuggerOff,
};
fromId.mockReturnValue({
id: 42,
isDestroyed: () => destroyed,
getType: () => "webview",
getURL: () => "http://localhost:3200/",
getTitle: () => "Preview",
isLoading: () => false,
isDevToolsOpened: () => false,
getZoomFactor: () => 1,
setZoomFactor: vi.fn(),
setAudioMuted: vi.fn(),
isCurrentlyAudible: () => false,
reload: vi.fn(),
loadURL: vi.fn(async () => undefined),
on: vi.fn(),
off: vi.fn(),
ipc: { on: vi.fn(), off: vi.fn() },
send: webviewSend,
navigationHistory: { canGoBack: () => false, canGoForward: () => false },
setWindowOpenHandler: vi.fn(),
get debugger() {
if (destroyed) throw new Error("Object has been destroyed");
return wcDebugger;
},
} as never);
yield* manager.createTab("tab_pinned_debugger");
yield* manager.registerWebview("tab_pinned_debugger", 42);
yield* manager.setColorScheme("tab_pinned_debugger", "dark");
expect(attached).toBe(true);
destroyed = true;

yield* manager.navigate("tab_pinned_debugger", "https://example.com/");

expect(debuggerOff).toHaveBeenCalledWith("message", expect.any(Function));
expect(debuggerDetach).toHaveBeenCalledOnce();
}),
),
);

effectIt.effect("does not let destroyed-webview cleanup detach a same-id replacement", () =>
withManager((manager) =>
Effect.gen(function* () {
Expand Down
32 changes: 20 additions & 12 deletions apps/desktop/src/preview/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,12 @@ interface PickSession {

interface BrowserControlSession {
readonly webContentsId: number;
// Pins the WebContents' Debugger wrapper for the session's lifetime.
// Electron's Debugger is GC-managed but registered with Chromium as a raw
// DevToolsAgentHostClient pointer; collecting it while attached crashes the
// browser process (electron/electron#53376). Detach must also go through
// this reference: `wc.debugger` throws once the WebContents is destroyed.
readonly debugger: Electron.Debugger;
readonly semaphore: Semaphore.Semaphore;
readonly scope: Scope.Closeable;
readonly onMessage: (
Expand Down Expand Up @@ -1184,6 +1190,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
const createControlSession = Effect.fn("PreviewManager.createControlSession")(function* () {
const semaphore = yield* Semaphore.make(1);
const scope = yield* Scope.fork(parentScope, "sequential");
const wcDebugger = wc.debugger;
const handleDebuggerMessage = Effect.fnUntraced(function* (
method: string,
params: Record<string, unknown>,
Expand All @@ -1196,7 +1203,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
operation: "ackScreencastFrame",
webContentsId: wc.id,
},
() => wc.debugger.sendCommand("Page.screencastFrameAck", { sessionId }),
() => wcDebugger.sendCommand("Page.screencastFrameAck", { sessionId }),
).pipe(Effect.ignore);
}
const tabId = yield* tabIdForWebContents(wc.id);
Expand Down Expand Up @@ -1244,15 +1251,16 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
}),
),
attempt({ operation: "detachControlSession", webContentsId: wc.id }, () => {
wc.debugger.off("message", onMessage);
if (wc.debugger.isAttached()) wc.debugger.detach();
wcDebugger.off("message", onMessage);
if (wcDebugger.isAttached()) wcDebugger.detach();
}).pipe(Effect.ignore),
],
{ discard: true },
),
);
const control: BrowserControlSession = {
webContentsId: wc.id,
debugger: wcDebugger,
semaphore,
scope,
onMessage,
Expand All @@ -1268,15 +1276,15 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
}),
);
yield* attempt({ operation: "attachDebuggerListeners", webContentsId: wc.id }, () => {
wc.debugger.on("message", onMessage);
wc.debugger.attach("1.3");
wcDebugger.on("message", onMessage);
wcDebugger.attach("1.3");
});
yield* Effect.all(
["Runtime.enable", "Accessibility.enable", "Network.enable", "Log.enable"].map(
(method) =>
attemptPromise(
{ operation: `initializeDebugger.${method}`, webContentsId: wc.id },
() => wc.debugger.sendCommand(method),
() => wcDebugger.sendCommand(method),
),
),
{ concurrency: "unbounded", discard: true },
Expand Down Expand Up @@ -1365,7 +1373,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
}
const result = yield* attemptPromise(
{ operation: `${action}.${method}`, tabId, webContentsId: wc.id },
() => wc.debugger.sendCommand(method, commandParams),
() => control.debugger.sendCommand(method, commandParams),
);
const after = (yield* Ref.get(controlEpochRef)).get(tabId) ?? 0;
if (after !== epoch) {
Expand All @@ -1389,7 +1397,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
tabId,
webContentsId: wc.id,
},
() => wc.debugger.sendCommand(method, commandParams),
() => control.debugger.sendCommand(method, commandParams),
);
},
);
Expand Down Expand Up @@ -2578,9 +2586,9 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
wc: Electron.WebContents,
colorScheme: DesktopPreviewColorScheme,
) {
yield* ensureControlSession(wc);
const control = yield* ensureControlSession(wc);
yield* attemptPromise({ operation: "applyColorScheme", tabId, webContentsId: wc.id }, () =>
wc.debugger.sendCommand("Emulation.setEmulatedMedia", {
control.debugger.sendCommand("Emulation.setEmulatedMedia", {
features: [
{
name: "prefers-color-scheme",
Expand All @@ -2600,15 +2608,15 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function
Effect.gen(function* () {
const beforeAttach = (yield* SynchronizedRef.get(tabsRef)).get(tabId);
if (beforeAttach?.webContentsId !== wc.id) return;
yield* ensureControlSession(wc);
const control = yield* ensureControlSession(wc);
const afterAttach = (yield* SynchronizedRef.get(tabsRef)).get(tabId);
if (afterAttach?.webContentsId !== wc.id) {
yield* detachControlSession(wc.id);
return;
}
if (afterAttach.colorScheme !== "system") {
yield* attemptPromise({ operation: "applyColorScheme", tabId, webContentsId: wc.id }, () =>
wc.debugger.sendCommand("Emulation.setEmulatedMedia", {
control.debugger.sendCommand("Emulation.setEmulatedMedia", {
features: [
{
name: "prefers-color-scheme",
Expand Down
Binary file added apps/marketing/public/app-desktop.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/marketing/public/harnesses/antigravity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed apps/marketing/public/updated-screenshot.webp
Binary file not shown.
24 changes: 10 additions & 14 deletions apps/marketing/src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface Props {

const {
title = "T3 Code",
description = "T3 Code The open-source control plane for coding agents.",
description = "T3 Code. The open-source control plane for coding agents.",
pageClass,
} = Astro.props;
---
Expand Down Expand Up @@ -268,21 +268,17 @@ const {
}
}

@keyframes pulse {
50% { opacity: 0.4; }
/* Page-load sequence: [data-rise] plays once with delay --d. */
@keyframes rise {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: none; }
}

@keyframes spin {
to { transform: rotate(360deg); }
}

@keyframes floatDrift {
0%, 100% { translate: 0 0; }
50% { translate: 0 -10px; }
[data-rise] {
animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
animation-delay: var(--d, 0ms);
}

@keyframes blink {
50% { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
[data-rise] { animation: none; }
}
</style>

Expand Down
4 changes: 2 additions & 2 deletions apps/marketing/src/lib/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ export const ANDROID_PLAY_STORE_URL =
"https://play.google.com/store/apps/details?id=com.t3tools.t3code";

export const MARKETING_STATS = {
githubStars: "14k+",
users: "100,000",
githubStars: "21k+",
users: "200,000",
} as const;
Loading
Loading