Skip to content

Commit 2d2cce0

Browse files
committed
sync: merge 6 new upstream commits from pingdotgg/t3code
Upstream additions: - fix: Align token usage metrics for both Claude and Codex (pingdotgg#1943) - fix(web): allow concurrent browser tests to retry ports (pingdotgg#1951) - fix: quote editor launch args on Windows for paths with spaces (pingdotgg#1805) - Coalesce status refreshes by remote (pingdotgg#1940) - chore(desktop): separate dev AppUserModelID on Windows (pingdotgg#1934) - feat(web): add extensible command palette (pingdotgg#1103) Fork adaptations: - Took upstream's extensible command palette (replaces fork's simpler version) - Took upstream's extracted thread sort logic (Sidebar.logic.ts → lib/threadSort.ts) - Inline ModelUsage/NonNullableUsage types (not yet re-exported from SDK) - Updated claude-agent-sdk to 0.2.104 - Made Thread.archivedAt required (matches upstream)
2 parents 4e1917b + 7a00846 commit 2d2cce0

34 files changed

+2517
-1010
lines changed

KEYBINDINGS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ See the full schema for more details: [`packages/contracts/src/keybindings.ts`](
2323
{ "key": "mod+d", "command": "terminal.split", "when": "terminalFocus" },
2424
{ "key": "mod+n", "command": "terminal.new", "when": "terminalFocus" },
2525
{ "key": "mod+w", "command": "terminal.close", "when": "terminalFocus" },
26+
{ "key": "mod+k", "command": "commandPalette.toggle", "when": "!terminalFocus" },
2627
{ "key": "mod+n", "command": "chat.new", "when": "!terminalFocus" },
2728
{ "key": "mod+shift+o", "command": "chat.new", "when": "!terminalFocus" },
2829
{ "key": "mod+shift+n", "command": "chat.newLocal", "when": "!terminalFocus" },
@@ -50,6 +51,7 @@ Invalid rules are ignored. Invalid config files are ignored. Warnings are logged
5051
- `terminal.split`: split terminal (in focused terminal context by default)
5152
- `terminal.new`: create new terminal (in focused terminal context by default)
5253
- `terminal.close`: close/kill the focused terminal (in focused terminal context by default)
54+
- `commandPalette.toggle`: open or close the global command palette
5355
- `chat.new`: create a new chat thread preserving the active thread's branch/worktree state
5456
- `chat.newLocal`: create a new chat thread for the active project in a new environment (local/worktree determined by app settings (default `local`))
5557
- `editor.openFavorite`: open current project/worktree in the last-used editor

apps/desktop/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const DESKTOP_SCHEME = "t3";
104104
const ROOT_DIR = Path.resolve(__dirname, "../../..");
105105
const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
106106
const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
107-
const APP_USER_MODEL_ID = "com.t3tools.t3code";
107+
const APP_USER_MODEL_ID = isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code";
108108
const LINUX_DESKTOP_ENTRY_NAME = isDevelopment ? "t3code-dev.desktop" : "t3code.desktop";
109109
const LINUX_WM_CLASS = isDevelopment ? "t3code-dev" : "t3code";
110110
const USER_DATA_DIR_NAME = isDevelopment ? "t3code-dev" : "t3code";

apps/server/src/git/Layers/GitCore.test.ts

Lines changed: 82 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ it.layer(TestLayer)("git integration", (it) => {
826826
}),
827827
);
828828

829-
it.effect("shares upstream refreshes across worktrees that use the same git common dir", () =>
829+
it.effect("coalesces upstream refreshes across sibling worktrees on the same remote", () =>
830830
Effect.gen(function* () {
831831
const ok = (stdout = "") =>
832832
Effect.succeed({
@@ -845,7 +845,9 @@ it.layer(TestLayer)("git integration", (it) => {
845845
input.args[2] === "--symbolic-full-name" &&
846846
input.args[3] === "@{upstream}"
847847
) {
848-
return ok("origin/main\n");
848+
return ok(
849+
input.cwd === "/repo/worktrees/pr-123" ? "origin/feature/pr-123\n" : "origin/main\n",
850+
);
849851
}
850852
if (input.args[0] === "remote") {
851853
return ok("origin\n");
@@ -856,10 +858,22 @@ it.layer(TestLayer)("git integration", (it) => {
856858
if (input.args[0] === "--git-dir" && input.args[2] === "fetch") {
857859
fetchCount += 1;
858860
expect(input.cwd).toBe("/repo");
861+
expect(input.args).toEqual([
862+
"--git-dir",
863+
"/repo/.git",
864+
"fetch",
865+
"--quiet",
866+
"--no-tags",
867+
"origin",
868+
]);
859869
return ok();
860870
}
861871
if (input.operation === "GitCore.statusDetails.status") {
862-
return ok("# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n");
872+
return ok(
873+
input.cwd === "/repo/worktrees/pr-123"
874+
? "# branch.head feature/pr-123\n# branch.upstream origin/feature/pr-123\n# branch.ab +0 -0\n"
875+
: "# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n",
876+
);
863877
}
864878
if (
865879
input.operation === "GitCore.statusDetails.unstagedNumstat" ||
@@ -886,70 +900,80 @@ it.layer(TestLayer)("git integration", (it) => {
886900
}),
887901
);
888902

889-
it.effect("briefly backs off failed upstream refreshes across sibling worktrees", () =>
890-
Effect.gen(function* () {
891-
const ok = (stdout = "") =>
892-
Effect.succeed({
893-
code: 0,
894-
stdout,
895-
stderr: "",
896-
stdoutTruncated: false,
897-
stderrTruncated: false,
898-
});
903+
it.effect(
904+
"briefly backs off failed upstream refreshes across sibling worktrees on one remote",
905+
() =>
906+
Effect.gen(function* () {
907+
const ok = (stdout = "") =>
908+
Effect.succeed({
909+
code: 0,
910+
stdout,
911+
stderr: "",
912+
stdoutTruncated: false,
913+
stderrTruncated: false,
914+
});
899915

900-
let fetchCount = 0;
901-
const core = yield* makeIsolatedGitCore((input) => {
902-
if (
903-
input.args[0] === "rev-parse" &&
904-
input.args[1] === "--abbrev-ref" &&
905-
input.args[2] === "--symbolic-full-name" &&
906-
input.args[3] === "@{upstream}"
907-
) {
908-
return ok("origin/main\n");
909-
}
910-
if (input.args[0] === "remote") {
911-
return ok("origin\n");
912-
}
913-
if (input.args[0] === "rev-parse" && input.args[1] === "--git-common-dir") {
914-
return ok("/repo/.git\n");
915-
}
916-
if (input.args[0] === "--git-dir" && input.args[2] === "fetch") {
917-
fetchCount += 1;
916+
let fetchCount = 0;
917+
const core = yield* makeIsolatedGitCore((input) => {
918+
if (
919+
input.args[0] === "rev-parse" &&
920+
input.args[1] === "--abbrev-ref" &&
921+
input.args[2] === "--symbolic-full-name" &&
922+
input.args[3] === "@{upstream}"
923+
) {
924+
return ok(
925+
input.cwd === "/repo/worktrees/pr-123"
926+
? "origin/feature/pr-123\n"
927+
: "origin/main\n",
928+
);
929+
}
930+
if (input.args[0] === "remote") {
931+
return ok("origin\n");
932+
}
933+
if (input.args[0] === "rev-parse" && input.args[1] === "--git-common-dir") {
934+
return ok("/repo/.git\n");
935+
}
936+
if (input.args[0] === "--git-dir" && input.args[2] === "fetch") {
937+
fetchCount += 1;
938+
return Effect.fail(
939+
new GitCommandError({
940+
operation: input.operation,
941+
command: `git ${input.args.join(" ")}`,
942+
cwd: input.cwd,
943+
detail: "simulated fetch timeout",
944+
}),
945+
);
946+
}
947+
if (input.operation === "GitCore.statusDetails.status") {
948+
return ok(
949+
input.cwd === "/repo/worktrees/pr-123"
950+
? "# branch.head feature/pr-123\n# branch.upstream origin/feature/pr-123\n# branch.ab +0 -0\n"
951+
: "# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n",
952+
);
953+
}
954+
if (
955+
input.operation === "GitCore.statusDetails.unstagedNumstat" ||
956+
input.operation === "GitCore.statusDetails.stagedNumstat"
957+
) {
958+
return ok();
959+
}
960+
if (input.operation === "GitCore.statusDetails.defaultRef") {
961+
return ok("refs/remotes/origin/main\n");
962+
}
918963
return Effect.fail(
919964
new GitCommandError({
920965
operation: input.operation,
921966
command: `git ${input.args.join(" ")}`,
922967
cwd: input.cwd,
923-
detail: "simulated fetch timeout",
968+
detail: "Unexpected git command in refresh failure cooldown test.",
924969
}),
925970
);
926-
}
927-
if (input.operation === "GitCore.statusDetails.status") {
928-
return ok("# branch.head main\n# branch.upstream origin/main\n# branch.ab +0 -0\n");
929-
}
930-
if (
931-
input.operation === "GitCore.statusDetails.unstagedNumstat" ||
932-
input.operation === "GitCore.statusDetails.stagedNumstat"
933-
) {
934-
return ok();
935-
}
936-
if (input.operation === "GitCore.statusDetails.defaultRef") {
937-
return ok("refs/remotes/origin/main\n");
938-
}
939-
return Effect.fail(
940-
new GitCommandError({
941-
operation: input.operation,
942-
command: `git ${input.args.join(" ")}`,
943-
cwd: input.cwd,
944-
detail: "Unexpected git command in refresh failure cooldown test.",
945-
}),
946-
);
947-
});
971+
});
948972

949-
yield* core.statusDetails("/repo/worktrees/main");
950-
yield* core.statusDetails("/repo/worktrees/pr-123");
951-
expect(fetchCount).toBe(1);
952-
}),
973+
yield* core.statusDetails("/repo/worktrees/main");
974+
yield* core.statusDetails("/repo/worktrees/pr-123");
975+
expect(fetchCount).toBe(1);
976+
}),
953977
);
954978

955979
it.effect("throws when branch does not exist", () =>
@@ -1047,7 +1071,6 @@ it.layer(TestLayer)("git integration", (it) => {
10471071
"--quiet",
10481072
"--no-tags",
10491073
remoteName,
1050-
`+refs/heads/${featureBranch}:refs/remotes/${remoteName}/${featureBranch}`,
10511074
]);
10521075
}),
10531076
);

apps/server/src/git/Layers/GitCore.ts

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,9 @@ type TraceTailState = {
7979
remainder: string;
8080
};
8181

82-
class StatusUpstreamRefreshCacheKey extends Data.Class<{
82+
class StatusRemoteRefreshCacheKey extends Data.Class<{
8383
gitCommonDir: string;
84-
upstreamRef: string;
8584
remoteName: string;
86-
upstreamBranch: string;
8785
}> {}
8886

8987
interface ExecuteGitOptions {
@@ -920,17 +918,16 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
920918
);
921919
});
922920

923-
const fetchUpstreamRefForStatus = (
921+
const fetchRemoteForStatus = (
924922
gitCommonDir: string,
925-
upstream: { upstreamRef: string; remoteName: string; upstreamBranch: string },
923+
remoteName: string,
926924
): Effect.Effect<void, GitCommandError> => {
927-
const refspec = `+refs/heads/${upstream.upstreamBranch}:refs/remotes/${upstream.upstreamRef}`;
928925
const fetchCwd =
929926
path.basename(gitCommonDir) === ".git" ? path.dirname(gitCommonDir) : gitCommonDir;
930927
return executeGit(
931-
"GitCore.fetchUpstreamRefForStatus",
928+
"GitCore.fetchRemoteForStatus",
932929
fetchCwd,
933-
["--git-dir", gitCommonDir, "fetch", "--quiet", "--no-tags", upstream.remoteName, refspec],
930+
["--git-dir", gitCommonDir, "fetch", "--quiet", "--no-tags", remoteName],
934931
{
935932
allowNonZeroExit: true,
936933
timeoutMs: Duration.toMillis(STATUS_UPSTREAM_REFRESH_TIMEOUT),
@@ -946,18 +943,14 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
946943
return path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(cwd, gitCommonDir);
947944
});
948945

949-
const refreshStatusUpstreamCacheEntry = Effect.fn("refreshStatusUpstreamCacheEntry")(function* (
950-
cacheKey: StatusUpstreamRefreshCacheKey,
946+
const refreshStatusRemoteCacheEntry = Effect.fn("refreshStatusRemoteCacheEntry")(function* (
947+
cacheKey: StatusRemoteRefreshCacheKey,
951948
) {
952-
yield* fetchUpstreamRefForStatus(cacheKey.gitCommonDir, {
953-
upstreamRef: cacheKey.upstreamRef,
954-
remoteName: cacheKey.remoteName,
955-
upstreamBranch: cacheKey.upstreamBranch,
956-
});
949+
yield* fetchRemoteForStatus(cacheKey.gitCommonDir, cacheKey.remoteName);
957950
return true as const;
958951
});
959952

960-
const statusUpstreamRefreshCache = yield* Cache.makeWith(refreshStatusUpstreamCacheEntry, {
953+
const statusRemoteRefreshCache = yield* Cache.makeWith(refreshStatusRemoteCacheEntry, {
961954
capacity: STATUS_UPSTREAM_REFRESH_CACHE_CAPACITY,
962955
// Keep successful refreshes warm and briefly back off failed refreshes to avoid retry storms.
963956
timeToLive: (exit) =>
@@ -973,12 +966,10 @@ export const makeGitCore = Effect.fn("makeGitCore")(function* (options?: {
973966
if (!upstream) return;
974967
const gitCommonDir = yield* resolveGitCommonDir(cwd);
975968
yield* Cache.get(
976-
statusUpstreamRefreshCache,
977-
new StatusUpstreamRefreshCacheKey({
969+
statusRemoteRefreshCache,
970+
new StatusRemoteRefreshCacheKey({
978971
gitCommonDir,
979-
upstreamRef: upstream.upstreamRef,
980972
remoteName: upstream.remoteName,
981-
upstreamBranch: upstream.upstreamBranch,
982973
}),
983974
);
984975
});

apps/server/src/keybindings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray<KeybindingRule> = [
6262
{ key: "mod+n", command: "terminal.new", when: "terminalFocus" },
6363
{ key: "mod+w", command: "terminal.close", when: "terminalFocus" },
6464
{ key: "mod+d", command: "diff.toggle", when: "!terminalFocus" },
65+
{ key: "mod+k", command: "commandPalette.toggle", when: "!terminalFocus" },
6566
{ key: "mod+n", command: "chat.new", when: "!terminalFocus" },
6667
{ key: "mod+shift+o", command: "chat.new", when: "!terminalFocus" },
6768
{ key: "mod+shift+n", command: "chat.newLocal", when: "!terminalFocus" },

apps/server/src/open.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,16 @@ export const launchDetached = (launch: EditorLaunch) =>
269269
yield* Effect.callback<void, OpenError>((resume) => {
270270
let child;
271271
try {
272-
child = spawn(launch.command, [...launch.args], {
273-
detached: true,
274-
stdio: "ignore",
275-
shell: process.platform === "win32",
276-
});
272+
const isWin32 = process.platform === "win32";
273+
child = spawn(
274+
launch.command,
275+
isWin32 ? launch.args.map((a) => `"${a}"`) : [...launch.args],
276+
{
277+
detached: true,
278+
stdio: "ignore",
279+
shell: isWin32,
280+
},
281+
);
277282
} catch (error) {
278283
return resume(
279284
Effect.fail(new OpenError({ message: "failed to spawn detached process", cause: error })),

0 commit comments

Comments
 (0)