Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8e8e06f
Group threads by worktree
jakeleventhal Jul 11, 2026
ac51b1e
Normalize workspace path comparisons
jakeleventhal Jul 11, 2026
23fde8d
Expose the primary Git checkout path
jakeleventhal Jul 11, 2026
419ea32
Preserve active worktree labels during ref loading
jakeleventhal Jul 11, 2026
d5b174a
Apply selected project checkout metadata
jakeleventhal Jul 11, 2026
36ceb7f
Resolve new-thread defaults by project
jakeleventhal Jul 11, 2026
a572b9a
Read defaults from the target environment
jakeleventhal Jul 11, 2026
b0b6e34
Wire target defaults through thread entry points
jakeleventhal Jul 11, 2026
f248e3f
Await active project ref discovery
jakeleventhal Jul 11, 2026
34e8a0b
Cover active ref loading before draft creation
jakeleventhal Jul 11, 2026
bbddf59
Avoid cross-project checkout fallbacks
jakeleventhal Jul 11, 2026
05e991d
Cover failed target checkout discovery
jakeleventhal Jul 11, 2026
dc4533d
Seed local drafts from the checked-out branch
jakeleventhal Jul 11, 2026
9df8ad2
Build sidebar workspace identity per project
jakeleventhal Jul 11, 2026
0ed7c05
Cover grouped sidebar member checkouts
jakeleventhal Jul 11, 2026
ed85bdd
Keep sidebar checkout groups distinct without refs
jakeleventhal Jul 11, 2026
0caeb4a
Align sidebar shortcuts with grouped row order
jakeleventhal Jul 11, 2026
34121ea
Honor pending existing-worktree selections on send
jakeleventhal Jul 11, 2026
561a842
Compare Windows workspace paths case-insensitively.
jakeleventhal Jul 12, 2026
03a0ba5
Keep active worktrees visible for partial ref pages.
jakeleventhal Jul 12, 2026
70ab059
Honor pending worktree selections in the branch toolbar.
jakeleventhal Jul 12, 2026
0a062b9
Stop labeling linked checkouts as Main checkout.
jakeleventhal Jul 12, 2026
81a3d42
fix(web): keep pending worktree picker unlocked
jakeleventhal Jul 12, 2026
36ef3c7
fix(web): forward pending worktree to branch picker
jakeleventhal Jul 12, 2026
0225976
fix(web): normalize Windows workspace path casing
jakeleventhal Jul 12, 2026
6b6bab3
perf(web): skip refs for hidden sidebar rows
jakeleventhal Jul 12, 2026
052e138
fix(web): load all workspace refs for picker
jakeleventhal Jul 12, 2026
5979c36
fix(server): propagate remote base lookup failures
jakeleventhal Jul 12, 2026
6376339
fix(web): retry failed ref pagination pages
jakeleventhal Jul 12, 2026
0915a06
fix(web): retry initial ref page failures
jakeleventhal Jul 12, 2026
35768db
fix(web): keep retrying failed ref pages
jakeleventhal Jul 12, 2026
53ab40f
refactor(server): type missing remote ref failures
jakeleventhal Jul 12, 2026
4ade26b
fix(web): bound new-thread checkout discovery
jakeleventhal Jul 12, 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 apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const clientSettings: ClientSettings = {
},
sidebarProjectSortOrder: "manual",
sidebarThreadSortOrder: "created_at",
sidebarThreadGroupingMode: "separate",
sidebarThreadPreviewCount: 6,
timestampFormat: "24-hour",
wordWrap: true,
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/git/GitWorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class GitWorkflowService extends Context.Service<
readonly fallbackRemoteName: string;
}) => Effect.Effect<
{ readonly commitSha: string; readonly remoteRefName: string },
GitCommandError
GitCommandError | GitVcsDriver.RemoteTrackingRefNotFoundError
>;
readonly removeWorktree: (
input: VcsRemoveWorktreeInput,
Expand Down
177 changes: 177 additions & 0 deletions apps/server/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6338,6 +6338,183 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect("falls back to a local base when the branch is not available on origin", () =>
Effect.gen(function* () {
const dispatchedCommands: Array<OrchestrationCommand> = [];
const createWorktree = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
Effect.succeed({
worktree: {
refName: "t3code/bootstrap-local-base",
path: "/tmp/bootstrap-local-base",
},
}),
);
const missingRemoteBranch = new GitVcsDriver.RemoteTrackingRefNotFoundError({
cwd: "/tmp/project",
remoteRefName: "origin/t3code/local-only",
});

yield* buildAppUnderTest({
layers: {
gitVcsDriver: {
fetchRemote: () => Effect.void,
resolveRemoteTrackingCommit: () => Effect.fail(missingRemoteBranch),
createWorktree,
},
vcsStatusBroadcaster: {
refreshStatus: () =>
Effect.succeed({
isRepo: true,
hasPrimaryRemote: true,
isDefaultRef: false,
refName: "t3code/bootstrap-local-base",
hasWorkingTreeChanges: false,
workingTree: { files: [], insertions: 0, deletions: 0 },
hasUpstream: false,
aheadCount: 0,
behindCount: 0,
pr: null,
}),
},
orchestrationEngine: {
dispatch: (command) =>
Effect.sync(() => {
dispatchedCommands.push(command);
return { sequence: dispatchedCommands.length };
}),
readEvents: () => Stream.empty,
},
},
});

const wsUrl = yield* getWsServerUrl("/ws");
const response = yield* Effect.scoped(
withWsRpcClient(wsUrl, (client) =>
client[ORCHESTRATION_WS_METHODS.dispatchCommand]({
type: "thread.turn.start",
commandId: CommandId.make("cmd-bootstrap-local-base"),
threadId: ThreadId.make("thread-bootstrap-local-base"),
message: {
messageId: MessageId.make("msg-bootstrap-local-base"),
role: "user",
text: "hello",
attachments: [],
},
modelSelection: defaultModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
bootstrap: {
createThread: {
projectId: defaultProjectId,
title: "Bootstrap Local Base",
modelSelection: defaultModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: "t3code/local-only",
worktreePath: null,
createdAt: "2026-01-01T00:00:00.000Z",
},
prepareWorktree: {
projectCwd: "/tmp/project",
baseBranch: "t3code/local-only",
branch: "t3code/bootstrap-local-base",
startFromOrigin: true,
},
},
createdAt: "2026-01-01T00:00:00.000Z",
}),
),
);

assert.equal(response.sequence, 3);
assert.deepEqual(createWorktree.mock.calls[0]?.[0], {
cwd: "/tmp/project",
refName: "t3code/local-only",
newRefName: "t3code/bootstrap-local-base",
baseRefName: "t3code/local-only",
path: null,
});
assert.deepEqual(
dispatchedCommands.map((command) => command.type),
["thread.create", "thread.meta.update", "thread.turn.start"],
);
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect("propagates unexpected remote base resolution failures", () =>
Effect.gen(function* () {
const createWorktree = vi.fn(
(_: Parameters<GitVcsDriver.GitVcsDriver["Service"]["createWorktree"]>[0]) =>
Effect.succeed({
worktree: {
refName: "t3code/bootstrap-unexpected-failure",
path: "/tmp/bootstrap-unexpected-failure",
},
}),
);
const unexpectedFailure = new GitCommandError({
operation: "GitVcsDriver.resolveRemoteTrackingCommit",
command: "git show-ref --verify --quiet refs/remotes/origin/main",
cwd: "/tmp/project",
detail: "Git remote tracking ref lookup failed.",
exitCode: 128,
});

yield* buildAppUnderTest({
layers: {
gitVcsDriver: {
fetchRemote: () => Effect.void,
resolveRemoteTrackingCommit: () => Effect.fail(unexpectedFailure),
createWorktree,
},
},
});

const wsUrl = yield* getWsServerUrl("/ws");
const failed = yield* Effect.scoped(
withWsRpcClient(wsUrl, (client) =>
client[ORCHESTRATION_WS_METHODS.dispatchCommand]({
type: "thread.turn.start",
commandId: CommandId.make("cmd-bootstrap-unexpected-failure"),
threadId: ThreadId.make("thread-bootstrap-unexpected-failure"),
message: {
messageId: MessageId.make("msg-bootstrap-unexpected-failure"),
role: "user",
text: "hello",
attachments: [],
},
modelSelection: defaultModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
bootstrap: {
createThread: {
projectId: defaultProjectId,
title: "Bootstrap Unexpected Failure",
modelSelection: defaultModelSelection,
runtimeMode: "full-access",
interactionMode: "default",
branch: "main",
worktreePath: null,
createdAt: "2026-01-01T00:00:00.000Z",
},
prepareWorktree: {
projectCwd: "/tmp/project",
baseBranch: "main",
branch: "t3code/bootstrap-unexpected-failure",
startFromOrigin: true,
},
},
createdAt: "2026-01-01T00:00:00.000Z",
}),
),
).pipe(Effect.match({ onFailure: () => true, onSuccess: () => false }));

assertTrue(failed);
assert.equal(createWorktree.mock.calls.length, 0);
}).pipe(Effect.provide(NodeHttpServer.layerTest)),
);

it.effect("records setup-script failures without aborting bootstrap turn start", () =>
Effect.gen(function* () {
const dispatchedCommands: Array<OrchestrationCommand> = [];
Expand Down
20 changes: 19 additions & 1 deletion apps/server/src/vcs/GitVcsDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { ChildProcessSpawner } from "effect/unstable/process";

import {
Expand Down Expand Up @@ -177,6 +178,20 @@ export interface GitResolveRemoteTrackingCommitResult {
remoteRefName: string;
}

export class RemoteTrackingRefNotFoundError extends Schema.TaggedErrorClass<RemoteTrackingRefNotFoundError>()(
"RemoteTrackingRefNotFoundError",
{
cwd: Schema.String,
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
remoteRefName: Schema.String,
},
) {
override get message(): string {
return `Remote tracking ref '${this.remoteRefName}' does not exist (${this.cwd}).`;
}
}

export const isRemoteTrackingRefNotFound = Schema.is(RemoteTrackingRefNotFoundError);

export interface GitSetBranchUpstreamInput {
cwd: string;
branch: string;
Expand Down Expand Up @@ -240,7 +255,10 @@ export class GitVcsDriver extends Context.Service<
readonly fetchRemote: (input: GitFetchRemoteInput) => Effect.Effect<void, GitCommandError>;
readonly resolveRemoteTrackingCommit: (
input: GitResolveRemoteTrackingCommitInput,
) => Effect.Effect<GitResolveRemoteTrackingCommitResult, GitCommandError>;
) => Effect.Effect<
GitResolveRemoteTrackingCommitResult,
GitCommandError | RemoteTrackingRefNotFoundError
>;
readonly fetchRemoteBranch: (
input: GitFetchRemoteBranchInput,
) => Effect.Effect<void, GitCommandError>;
Expand Down
17 changes: 17 additions & 0 deletions apps/server/src/vcs/GitVcsDriverCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
const cwd = yield* makeTmpDir();
yield* initRepoWithCommit(cwd);
const driver = yield* GitVcsDriver.GitVcsDriver;
const fileSystem = yield* FileSystem.FileSystem;

yield* driver.createRef({ cwd, refName: "feature/original" });
const switchRef = yield* driver.switchRef({ cwd, refName: "feature/original" });
Expand All @@ -615,6 +616,10 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
assert.equal(yield* git(cwd, ["branch", "--show-current"]), "feature/renamed");

const refs = yield* driver.listRefs({ cwd });
assert.equal(
refs.mainCheckoutPath ? yield* fileSystem.realPath(refs.mainCheckoutPath) : null,
yield* fileSystem.realPath(cwd),
);
assert.equal(
refs.refs.find((refName) => refName.name === "feature/renamed")?.current,
true,
Expand Down Expand Up @@ -757,6 +762,18 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
assert.deepEqual(explicitlyResolvedBase, resolvedBase);
assert.equal(yield* git(cwd, ["rev-parse", initialBranch]), beforeFetch);

const missingRemoteRef = yield* driver
.resolveRemoteTrackingCommit({
cwd,
refName: "missing-remote-ref",
fallbackRemoteName: "origin",
})
.pipe(Effect.flip);
assert.equal(GitVcsDriver.isRemoteTrackingRefNotFound(missingRemoteRef), true);
if (GitVcsDriver.isRemoteTrackingRefNotFound(missingRemoteRef)) {
assert.equal(missingRemoteRef.remoteRefName, "origin/missing-remote-ref");
}

const pathService = yield* Path.Path;
const worktreePath = pathService.join(
yield* makeTmpDir("git-fetched-worktrees-"),
Expand Down
42 changes: 39 additions & 3 deletions apps/server/src/vcs/GitVcsDriverCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2158,8 +2158,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
: null;

const worktreeMap = new Map<string, string>();
let mainCheckoutPath: string | null = null;
if (worktreeList.exitCode === 0) {
let currentPath: string | null = null;
let sawMainCheckout = false;
for (const line of worktreeList.stdout.split("\n")) {
if (line.startsWith("worktree ")) {
const candidatePath = line.slice("worktree ".length);
Expand All @@ -2168,6 +2170,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
Effect.orElseSucceed(() => false),
);
currentPath = exists ? candidatePath : null;
if (!sawMainCheckout) {
mainCheckoutPath = currentPath;
sawMainCheckout = true;
}
Comment thread
cursor[bot] marked this conversation as resolved.
} else if (line.startsWith("branch refs/heads/") && currentPath) {
worktreeMap.set(line.slice("branch refs/heads/".length), currentPath);
} else if (line === "") {
Expand All @@ -2178,14 +2184,15 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*

const localBranches = Arr.filterMap(localBranchResult.stdout.split("\n"), (line) => {
const refName = parseBranchLine(line);
const worktreePath = refName === null ? null : (worktreeMap.get(refName.name) ?? null);
return refName === null
? Result.failVoid
: Result.succeed({
name: refName.name,
current: refName.current,
isRemote: false,
isDefault: refName.name === defaultBranch,
worktreePath: worktreeMap.get(refName.name) ?? null,
worktreePath,
});
}).toSorted((a, b) => {
const aPriority = a.current ? 0 : a.isDefault ? 1 : 2;
Expand Down Expand Up @@ -2251,6 +2258,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
refs: [...refs.refs],
isRepo: true,
hasPrimaryRemote: remoteNames.includes("origin"),
mainCheckoutPath,
nextCursor: refs.nextCursor,
totalCount: refs.totalCount,
};
Expand Down Expand Up @@ -2336,10 +2344,38 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
);
const remoteRefName =
parsedRemoteRef?.remoteRef ?? `${input.fallbackRemoteName}/${input.refName}`;
const remoteRef = `refs/remotes/${remoteRefName}`;
const args = ["show-ref", "--verify", "--quiet", remoteRef];
const result = yield* executeGit(
"GitVcsDriver.resolveRemoteTrackingCommit",
input.cwd,
args,
{ allowNonZeroExit: true },
);
if (result.exitCode !== 0) {
if (result.exitCode === 1) {
return yield* new GitVcsDriver.RemoteTrackingRefNotFoundError({
cwd: input.cwd,
remoteRefName,
});
}
return yield* new GitCommandError({
...gitCommandContext({
operation: "GitVcsDriver.resolveRemoteTrackingCommit",
cwd: input.cwd,
args,
}),
detail: "Git remote tracking ref lookup failed.",
...(result.exitCode === null ? {} : { exitCode: result.exitCode }),
stdoutLength: result.stdout.length,
stderrLength: result.stderr.length,
});
}
const commitSha = yield* runGitStdout("GitVcsDriver.resolveRemoteTrackingCommit", input.cwd, [
"rev-parse",
"show-ref",
"--verify",
`refs/remotes/${remoteRefName}^{commit}`,
"--hash=40",
remoteRef,
]).pipe(Effect.map((stdout) => stdout.trim()));

return { commitSha, remoteRefName };
Expand Down
Loading
Loading