Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
db8d60f
fix(web): render transparent previews on white (#9463)
UtkarshUsername Sep 3, 2026
de025aa
fix(mobile): show loading and syncing in the working pill (#9466)
juliusmarminge Sep 3, 2026
36c4e9c
fix(server): keep a/ and b/ prefixes in rendered git patches (#9438)
Mnigos Sep 3, 2026
d2b6f3b
fix(server): full-access OpenCode threads no longer ask for approvals…
shivamhwp Sep 3, 2026
493fbb5
fix(web): reuse pull request list data while loading (#9467)
maria-rcks Sep 3, 2026
0372836
feat(web): let users turn off composer collapse on blur and scroll (#…
juliusmarminge Sep 3, 2026
373be93
fix(web): move workflow approval beside checks (#9465)
maria-rcks Sep 3, 2026
4b8b5d9
fix(desktop): refresh generated annotation styles (#9488)
juliusmarminge Sep 3, 2026
0869ad6
fix(web): let the PR reviewer and label search boxes take keystrokes …
juliusmarminge Sep 3, 2026
77138cf
fix(web): dont collapse composer when interacting with bottom row (#9…
extoci Sep 3, 2026
678f23a
fix(desktop): restore second-press quit fallback (#9485)
maria-rcks Sep 3, 2026
c726c30
fix(web): keep opencode icon hollow in collapsed composer (#9492)
maria-rcks Sep 3, 2026
12e8997
fix(web): keep agent browser preview visible (#9484)
maria-rcks Sep 3, 2026
409bc4f
fix(mobile): keep the machine glyph next to the environment label (#9…
juliusmarminge Sep 3, 2026
80b5373
fix(mobile): let back swipe pop from horizontal scroll edges (#9493)
juliusmarminge Sep 3, 2026
0aae1e2
fix(antigravity): discover legacy workspace skills (#9410)
Lucenx9 Sep 3, 2026
ef4cc60
fix(mobile): resolve Antigravity provider icon and normalize driver m…
Invictine Sep 3, 2026
e01c153
fix(antigravity): forward Google sign-in URLs from browser helper (#9…
WellyngtonF Sep 3, 2026
39449e5
feat(desktop): import browser cookies into a profile (#7255)
juliusmarminge Sep 3, 2026
ff58434
feat(desktop): import from Chrome, Edge, Brave, Vivaldi, Opera, Arc a…
juliusmarminge Sep 3, 2026
498ab9c
feat(desktop): resolve Chromium cookie keys on Linux (#7261)
juliusmarminge Sep 3, 2026
f25e442
fix(antigravity): allow slow runtime startup during setup (#9510)
t3dotgg Sep 3, 2026
baf67b6
fix(antigravity): keep model choices up to date (#9511)
t3dotgg Sep 3, 2026
eb334ca
fix(antigravity): handle native sign-in URLs on stderr (#9514)
t3dotgg Sep 3, 2026
8ea52c8
fix(antigravity): update managed runtime to 1.1.1 (#9509)
t3dotgg Sep 3, 2026
3653cb2
fix(desktop): address the browser import review left over from the st…
juliusmarminge Sep 3, 2026
f8a14b2
feat(antigravity): show subagent calls and results (#9515)
t3dotgg Sep 4, 2026
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
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,6 +51,9 @@ jobs:
- name: Typecheck
run: vpr typecheck

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Build desktop pipeline
run: vp run build:desktop

Expand DownExpand Up@@ -85,6 +88,9 @@ jobs:
- name: Ensure Electron runtime is installed
run: vp run --filter @t3tools/desktop ensure:electron

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

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

Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/cursor-hygiene-webhook.yml
Original file line numberDiff line numberDiff 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 }}"
8 changes: 6 additions & 2 deletions .github/workflows/release.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -216,6 +216,9 @@ jobs:
- name: Typecheck
run: vp run typecheck

- name: Install browser secret helper build libraries
run: sudo apt-get update && sudo apt-get install -y libsecret-1-dev pkg-config

- name: Test
run: vp run test

Expand DownExpand Up@@ -524,12 +527,13 @@ jobs:
exit $code
}

- name: Install ImageMagick
- name: Install Linux desktop build libraries
if: matrix.platform == 'linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libsecret-1-dev pkg-config
if ! command -v magick >/dev/null 2>&1 && ! command -v convert >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y imagemagick
fi

Expand Down
1 change: 1 addition & 0 deletions apps/desktop/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@
"@clerk/electron": "catalog:",
"@clerk/electron-passkeys": "catalog:",
"@effect/platform-node": "catalog:",
"@napi-rs/keyring": "^1.3.0",
"@t3tools/client-runtime": "workspace:*",
"@t3tools/contracts": "workspace:*",
"@t3tools/shared": "workspace:*",
Expand Down
103 changes: 103 additions & 0 deletions apps/desktop/scripts/browser-secret-native.test.mjs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
import * as NodeChildProcess from "node:child_process";
import * as NodeFS from "node:fs";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";
import * as NodeURL from "node:url";
import { afterAll, beforeAll, describe, expect, it } from "vite-plus/test";

// oxlint-disable-next-line t3code/no-global-process-runtime -- The native compiler targets the actual host; this script has no Effect runtime.
const hostArch = process.arch;
// oxlint-disable-next-line t3code/no-global-process-runtime -- Native compilation only runs on the actual Linux host.
const hostPlatform = process.platform;

describe.skipIf(hostPlatform !== "linux")("bundled libsecret helper", () => {
let directory;
let executable;
beforeAll(() => {
directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-browser-secret-test-"));
executable = NodePath.join(directory, "t3-browser-secret");
const root = NodeURL.fileURLToPath(new URL("../../../native/browser-secret/", import.meta.url));
const flags = NodeChildProcess.execFileSync(
"pkg-config",
["--cflags", "--libs", "libsecret-1"],
{
encoding: "utf8",
},
)
.trim()
.split(/\s+/);
NodeChildProcess.execFileSync(
process.env.CC || "cc",
[
"-std=c11",
"-Wall",
"-Wextra",
"-Werror",
NodePath.join(root, "main.c"),
NodePath.join(root, "test.c"),
"-Wl,--wrap=secret_service_search_sync",
"-Wl,--wrap=secret_item_get_locked",
"-Wl,--wrap=secret_item_get_secret",
"-o",
executable,
...flags,
],
{ stdio: "pipe" },
);
});
afterAll(() => {
if (directory) NodeFS.rmSync(directory, { recursive: true, force: true });
});

const run = (args) =>
NodeChildProcess.spawnSync(executable, args, {
env: { ...process.env, DBUS_SESSION_BUS_ADDRESS: "unix:path=/unused-test-bus" },
});

it("builds an executable for the requested architecture into a staged resource directory", () => {
const output = NodePath.join(directory, "resources", "browser-secret", "t3-browser-secret");
NodeChildProcess.execFileSync(process.execPath, [
NodeURL.fileURLToPath(new URL("./build-browser-secret.mjs", import.meta.url)),
"--arch",
hostArch,
"--output",
output,
]);
const header = NodeFS.readFileSync(output).subarray(0, 20);
expect(header.toString("hex", 0, 6)).toBe("7f454c460201");
expect(header.readUInt16LE(18)).toBe({ x64: 62, arm64: 183 }[hostArch]);
expect(NodeFS.statSync(output).mode & 0o111).not.toBe(0);
// Invalid arguments exit before the real executable could contact a keyring.
expect(NodeChildProcess.spawnSync(output, []).status).toBe(64);
});

it("preserves the exact secret bytes with no added or removed delimiter", () => {
const result = run(["success"]);
expect(result.status).toBe(0);
expect(result.stdout).toEqual(Buffer.from("secret\0with whitespace \t\r\n"));
expect(result.stderr.length).toBe(0);
});

for (const [scenario, code] of [
["missing", 2],
["empty", 2],
["locked", 3],
["cancelled", 3],
["denied", 3],
["unavailable", 4],
["unloaded", 4],
]) {
it(`reports ${scenario} without emitting a secret`, () => {
const result = run([scenario]);
expect(result.status).toBe(code);
expect(result.stdout.length).toBe(0);
});
}
it("rejects invalid arguments before accessing the keyring", () => {
for (const args of [[], [""], ["chrome", "extra"]]) {
const result = run(args);
expect(result.status).toBe(64);
expect(result.stdout.length).toBe(0);
}
});
});
66 changes: 66 additions & 0 deletions apps/desktop/scripts/build-browser-secret.mjs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
import * as NodeChildProcess from "node:child_process";
import * as NodeFS from "node:fs";
import * as NodePath from "node:path";
import * as NodeURL from "node:url";
import * as NodeUtil from "node:util";

// oxlint-disable-next-line t3code/no-global-process-runtime -- The native compiler targets the actual host; this script has no Effect runtime.
const hostArch = process.arch;
// oxlint-disable-next-line t3code/no-global-process-runtime -- Native compilation only runs on the actual Linux host.
const hostPlatform = process.platform;

const { values } = NodeUtil.parseArgs({
options: { output: { type: "string" }, arch: { type: "string", default: hostArch } },
});

if (hostPlatform === "linux") {
const machine = { x64: 62, arm64: 183 }[values.arch];
if (machine === undefined) throw new Error(`Unsupported Linux architecture: ${values.arch}`);
const root = NodeURL.fileURLToPath(new URL("../../../native/browser-secret/", import.meta.url));
const source = NodePath.resolve(root, "main.c");
const output = values.output ?? NodePath.resolve(root, "build", values.arch, "t3-browser-secret");
const matchesArchitecture = (file) => {
const header = NodeFS.readFileSync(file).subarray(0, 20);
return header.toString("hex", 0, 6) === "7f454c460201" && header.readUInt16LE(18) === machine;
};
let current = false;
try {
current =
NodeFS.statSync(output).mtimeMs >=
Math.max(
NodeFS.statSync(source).mtimeMs,
NodeFS.statSync(NodeURL.fileURLToPath(import.meta.url)).mtimeMs,
) && matchesArchitecture(output);
} catch {
/* The first build has no output yet. */
}
if (!current) {
let flags;
try {
flags = NodeChildProcess.execFileSync("pkg-config", ["--cflags", "--libs", "libsecret-1"], {
encoding: "utf8",
})
.trim()
.split(/\s+/);
} catch (cause) {
throw new Error(
"Building the Linux browser import helper requires pkg-config and libsecret development headers (Ubuntu/Debian: libsecret-1-dev).",
{ cause },
);
}
NodeFS.mkdirSync(NodePath.dirname(output), { recursive: true });
const temporary = `${output}.${process.pid}.tmp`;
try {
NodeChildProcess.execFileSync(
process.env.CC || "cc",
["-std=c11", "-O2", "-Wall", "-Wextra", "-Werror", source, "-o", temporary, ...flags],
{ stdio: "inherit" },
);
if (!matchesArchitecture(temporary))
throw new Error(`C compiler did not produce a Linux ${values.arch} executable.`);
NodeFS.renameSync(temporary, output);
} finally {
NodeFS.rmSync(temporary, { force: true });
}
}
}
6 changes: 6 additions & 0 deletions apps/desktop/scripts/dev-electron.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,12 @@ const remoteDebuggingPort = process.env.T3CODE_DESKTOP_REMOTE_DEBUGGING_PORT?.tr
// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone dev script has no Effect runtime.
const hostPlatform = NodeOS.platform();

NodeChildProcess.execFileSync(
process.execPath,
[NodePath.join(desktopDir, "scripts/build-browser-secret.mjs")],
{ stdio: "inherit" },
);

await waitForResources({
baseDir: desktopDir,
files: requiredFiles,
Expand Down
7 changes: 7 additions & 0 deletions apps/desktop/scripts/start-electron.mjs
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
import * as NodeChildProcess from "node:child_process";
import * as NodePath from "node:path";

import { desktopDir, resolveElectronLaunchCommand } from "./electron-launcher.mjs";

NodeChildProcess.execFileSync(
process.execPath,
[NodePath.join(desktopDir, "scripts/build-browser-secret.mjs")],
{ stdio: "inherit" },
);

const childEnv = { ...process.env };
delete childEnv.ELECTRON_RUN_AS_NODE;

Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/DesktopIpcHandlers.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,4 +103,6 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
for (const previewMethod of PreviewIpc.methods) {
yield* ipc.handle(previewMethod);
}
yield* ipc.handle(PreviewIpc.listBrowserImportSources);
yield* ipc.handle(PreviewIpc.importBrowserCookies);
});
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,6 +64,8 @@ export const PREVIEW_OPEN_DEVTOOLS_CHANNEL = "desktop:preview-open-devtools";
export const PREVIEW_CLEAR_COOKIES_CHANNEL = "desktop:preview-clear-cookies";
export const PREVIEW_CLEAR_CACHE_CHANNEL = "desktop:preview-clear-cache";
export const PREVIEW_GET_CONFIG_CHANNEL = "desktop:preview-get-config";
export const PREVIEW_IMPORT_SOURCES_CHANNEL = "desktop:preview-import-sources";
export const PREVIEW_IMPORT_COOKIES_CHANNEL = "desktop:preview-import-cookies";
export const PREVIEW_SET_ANNOTATION_THEME_CHANNEL = "desktop:preview-set-annotation-theme";
export const PREVIEW_PICK_ELEMENT_CHANNEL = "desktop:preview-pick-element";
export const PREVIEW_CANCEL_PICK_ELEMENT_CHANNEL = "desktop:preview-cancel-pick-element";
Expand Down
33 changes: 33 additions & 0 deletions apps/desktop/src/ipc/methods/preview.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,7 @@ import * as Schema from "effect/Schema";
import { beforeEach, describe, expect, it, vi } from "vite-plus/test";

import * as PreviewManager from "../../preview/Manager.ts";
import * as BrowserImport from "../../preview/BrowserImport/BrowserImport.ts";
import * as PreviewIpc from "./preview.ts";

const { fromPartition } = vi.hoisted(() => ({
Expand DownExpand Up@@ -80,6 +81,38 @@ describe("preview IPC methods", () => {
});
});

effectIt.effect("targets imports at the same partition tuple as the renderer", () => {
const received: Array<Parameters<BrowserImport.BrowserImport["Service"]["importCookies"]>[0]> =
[];
const browserImport = BrowserImport.BrowserImport.of({
listSources: Effect.succeed([]),
importCookies: (input) =>
Effect.sync(() => {
received.push(input);
return { imported: 0, skipped: 0, skippedDomains: [] };
}),
});
const request = (environmentId: string, targetProfileId: string) =>
PreviewIpc.importBrowserCookies.handler({
environmentId,
sourceId: "helium",
sourceProfileDirectory: "Default",
targetProfileId,
});

return Effect.gen(function* () {
yield* request("a", "b");
yield* request("a::b", DEFAULT_BROWSER_PROFILE_ID);

expect(received[0]).toMatchObject(PreviewIpc.resolvePartitionScope("a", "b"));
expect(received[1]).toMatchObject(
PreviewIpc.resolvePartitionScope("a::b", DEFAULT_BROWSER_PROFILE_ID),
);
expect(received[0]?.namespace).toBe("profile");
expect(received[1]?.namespace).toBeUndefined();
}).pipe(Effect.provideService(BrowserImport.BrowserImport, browserImport));
});

effectIt.effect("rejects invalid webContents ids before resolving the preview service", () =>
Effect.map(
PreviewIpc.registerWebview
Expand Down
Loading
Loading