authorgravatar for 35393046+lnieuwenhuis@users.noreply.github.comLars Nieuwenhuis <35393046+lnieuwenhuis@users.noreply.github.com> 2026-04-01 17:19:12+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-01 15:19:12+00:00
log0ac59d569d6df6568e5750f056a53ad32fd678b1
tree13b574bc0d6ca67154eaeccad0b9ebcc08e9dba6
parentd8aa2f85dfd76dde3698754f195c9b4bb9c613a3
signature Signed by PGP key B5690EEEBB952194

Feat: Added Trae IDE support for opening current working directory (#1656)


5 files changed, 87 insertions(+), 2 deletions(-)

apps/server/src/open.test.ts+20-1
...@@ -31,6 +31,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {...@@ -31,6 +31,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
31 args: ["/tmp/workspace"],31 args: ["/tmp/workspace"],
32 });32 });
3333
34 const traeLaunch = yield* resolveEditorLaunch(
35 { cwd: "/tmp/workspace", editor: "trae" },
36 "darwin",
37 );
38 assert.deepEqual(traeLaunch, {
39 command: "trae",
40 args: ["/tmp/workspace"],
41 });
42
34 const vscodeLaunch = yield* resolveEditorLaunch(43 const vscodeLaunch = yield* resolveEditorLaunch(
35 { cwd: "/tmp/workspace", editor: "vscode" },44 { cwd: "/tmp/workspace", editor: "vscode" },
36 "darwin",45 "darwin",
...@@ -89,6 +98,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {...@@ -89,6 +98,15 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
89 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],98 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
90 });99 });
91100
101 const traeLineAndColumn = yield* resolveEditorLaunch(
102 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "trae" },
103 "darwin",
104 );
105 assert.deepEqual(traeLineAndColumn, {
106 command: "trae",
107 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
108 });
109
92 const vscodeLineAndColumn = yield* resolveEditorLaunch(110 const vscodeLineAndColumn = yield* resolveEditorLaunch(
93 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" },111 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" },
94 "darwin",112 "darwin",
...@@ -256,6 +274,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {...@@ -256,6 +274,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
256 const path = yield* Path.Path;274 const path = yield* Path.Path;
257 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });275 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });
258276
277 yield* fs.writeFileString(path.join(dir, "trae.CMD"), "@echo off\r\n");
259 yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");278 yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");
260 yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");279 yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");
261 yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");280 yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");
...@@ -263,7 +282,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {...@@ -263,7 +282,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
263 PATH: dir,282 PATH: dir,
264 PATHEXT: ".COM;.EXE;.BAT;.CMD",283 PATHEXT: ".COM;.EXE;.BAT;.CMD",
265 });284 });
266 assert.deepEqual(editors, ["vscode-insiders", "vscodium", "file-manager"]);285 assert.deepEqual(editors, ["trae", "vscode-insiders", "vscodium", "file-manager"]);
267 }),286 }),
268 );287 );
269});288});
apps/web/src/components/ChatView.browser.tsx+46
...@@ -1407,6 +1407,52 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -1407,6 +1407,52 @@ describe("ChatView timeline estimator parity (full app)", () => {
1407 }1407 }
1408 });1408 });
14091409
1410 it("opens the project cwd with Trae when it is the only available editor", async () => {
1411 setDraftThreadWithoutWorktree();
1412
1413 const mounted = await mountChatView({
1414 viewport: DEFAULT_VIEWPORT,
1415 snapshot: createDraftOnlySnapshot(),
1416 configureFixture: (nextFixture) => {
1417 nextFixture.serverConfig = {
1418 ...nextFixture.serverConfig,
1419 availableEditors: ["trae"],
1420 };
1421 },
1422 });
1423
1424 try {
1425 await waitForServerConfigToApply();
1426 const openButton = await waitForElement(
1427 () =>
1428 Array.from(document.querySelectorAll("button")).find(
1429 (button) => button.textContent?.trim() === "Open",
1430 ) as HTMLButtonElement | null,
1431 "Unable to find Open button.",
1432 );
1433 await vi.waitFor(() => {
1434 expect(openButton.disabled).toBe(false);
1435 });
1436 openButton.click();
1437
1438 await vi.waitFor(
1439 () => {
1440 const openRequest = wsRequests.find(
1441 (request) => request._tag === WS_METHODS.shellOpenInEditor,
1442 );
1443 expect(openRequest).toMatchObject({
1444 _tag: WS_METHODS.shellOpenInEditor,
1445 cwd: "/repo/project",
1446 editor: "trae",
1447 });
1448 },
1449 { timeout: 8_000, interval: 16 },
1450 );
1451 } finally {
1452 await mounted.cleanup();
1453 }
1454 });
1455
1410 it("filters the open picker menu and opens VSCodium from the menu", async () => {1456 it("filters the open picker menu and opens VSCodium from the menu", async () => {
1411 setDraftThreadWithoutWorktree();1457 setDraftThreadWithoutWorktree();
14121458
apps/web/src/components/Icons.tsx+14
...@@ -20,6 +20,20 @@ export const CursorIcon: Icon = (props) => (...@@ -20,6 +20,20 @@ export const CursorIcon: Icon = (props) => (
20 </svg>20 </svg>
21);21);
2222
23export const TraeIcon: Icon = (props) => (
24 <svg {...props} viewBox="0 0 24 24" fill="currentColor">
25 {/* Back rectangle: left strip + bottom strip drawn separately — empty bottom-left corner is the gap between them */}
26 <rect x="1" y="4" width="3" height="14" />
27 <rect x="4" y="18" width="18" height="3" />
28 {/* Front frame: top bar + right bar only — left and bottom are replaced by the back strips above */}
29 <rect x="4" y="4" width="18" height="3" />
30 <rect x="19" y="7" width="3" height="11" />
31 {/* Two diamonds, offset slightly to the right within the open area */}
32 <path d="M11 10L13 12L11 14L9 12Z" />
33 <path d="M16 10L18 12L16 14L14 12Z" />
34 </svg>
35);
36
23export const VisualStudioCode: Icon = (props) => {37export const VisualStudioCode: Icon = (props) => {
24 const id = useId();38 const id = useId();
25 const maskId = `${id}-vscode-a`;39 const maskId = `${id}-vscode-a`;
apps/web/src/components/chat/OpenInPicker.tsx+6-1
...@@ -6,7 +6,7 @@ import { ChevronDownIcon, FolderClosedIcon } from "lucide-react";...@@ -6,7 +6,7 @@ import { ChevronDownIcon, FolderClosedIcon } from "lucide-react";
6import { Button } from "../ui/button";6import { Button } from "../ui/button";
7import { Group, GroupSeparator } from "../ui/group";7import { Group, GroupSeparator } from "../ui/group";
8import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "../ui/menu";8import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "../ui/menu";
9import { AntigravityIcon, CursorIcon, Icon, VisualStudioCode, Zed } from "../Icons";9import { AntigravityIcon, CursorIcon, Icon, TraeIcon, VisualStudioCode, Zed } from "../Icons";
10import { isMacPlatform, isWindowsPlatform } from "~/lib/utils";10import { isMacPlatform, isWindowsPlatform } from "~/lib/utils";
11import { readNativeApi } from "~/nativeApi";11import { readNativeApi } from "~/nativeApi";
1212
...@@ -17,6 +17,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor...@@ -17,6 +17,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor
17 Icon: CursorIcon,17 Icon: CursorIcon,
18 value: "cursor",18 value: "cursor",
19 },19 },
20 {
21 label: "Trae",
22 Icon: TraeIcon,
23 value: "trae",
24 },
20 {25 {
21 label: "VS Code",26 label: "VS Code",
22 Icon: VisualStudioCode,27 Icon: VisualStudioCode,
packages/contracts/src/editor.ts+1
...@@ -3,6 +3,7 @@ import { TrimmedNonEmptyString } from "./baseSchemas";...@@ -3,6 +3,7 @@ import { TrimmedNonEmptyString } from "./baseSchemas";
33
4export const EDITORS = [4export const EDITORS = [
5 { id: "cursor", label: "Cursor", command: "cursor", supportsGoto: true },5 { id: "cursor", label: "Cursor", command: "cursor", supportsGoto: true },
6 { id: "trae", label: "Trae", command: "trae", supportsGoto: true },
6 { id: "vscode", label: "VS Code", command: "code", supportsGoto: true },7 { id: "vscode", label: "VS Code", command: "code", supportsGoto: true },
7 {8 {
8 id: "vscode-insiders",9 id: "vscode-insiders",