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
9b96569
Group threads by worktree
jakeleventhal Jul 11, 2026
c254d5f
Normalize workspace path comparisons
jakeleventhal Jul 11, 2026
41f6aef
Expose the primary Git checkout path
jakeleventhal Jul 11, 2026
b9c9001
Preserve active worktree labels during ref loading
jakeleventhal Jul 11, 2026
8be96a9
Apply selected project checkout metadata
jakeleventhal Jul 11, 2026
25b1a4f
Resolve new-thread defaults by project
jakeleventhal Jul 11, 2026
38dfce3
Read defaults from the target environment
jakeleventhal Jul 11, 2026
afe1ae1
Wire target defaults through thread entry points
jakeleventhal Jul 11, 2026
1564da6
Await active project ref discovery
jakeleventhal Jul 11, 2026
6795535
Cover active ref loading before draft creation
jakeleventhal Jul 11, 2026
dc3738e
Avoid cross-project checkout fallbacks
jakeleventhal Jul 11, 2026
fd4f752
Cover failed target checkout discovery
jakeleventhal Jul 11, 2026
69592d8
Seed local drafts from the checked-out branch
jakeleventhal Jul 11, 2026
b818704
Build sidebar workspace identity per project
jakeleventhal Jul 11, 2026
0e60ec2
Cover grouped sidebar member checkouts
jakeleventhal Jul 11, 2026
854bf70
Keep sidebar checkout groups distinct without refs
jakeleventhal Jul 11, 2026
b4fa9a3
Align sidebar shortcuts with grouped row order
jakeleventhal Jul 11, 2026
9145cbb
Honor pending existing-worktree selections on send
jakeleventhal Jul 11, 2026
fff90fc
Compare Windows workspace paths case-insensitively.
jakeleventhal Jul 12, 2026
8d8e6d5
Keep active worktrees visible for partial ref pages.
jakeleventhal Jul 12, 2026
277a5e4
Honor pending worktree selections in the branch toolbar.
jakeleventhal Jul 12, 2026
3abce12
Stop labeling linked checkouts as Main checkout.
jakeleventhal Jul 12, 2026
747e319
fix(web): keep pending worktree picker unlocked
jakeleventhal Jul 12, 2026
5e3872d
fix(web): forward pending worktree to branch picker
jakeleventhal Jul 12, 2026
8b6e225
fix(web): normalize Windows workspace path casing
jakeleventhal Jul 12, 2026
9d2ed11
perf(web): skip refs for hidden sidebar rows
jakeleventhal Jul 12, 2026
9644cd8
fix(web): load all workspace refs for picker
jakeleventhal Jul 12, 2026
770796f
fix(server): propagate remote base lookup failures
jakeleventhal Jul 12, 2026
21c619d
fix(web): retry failed ref pagination pages
jakeleventhal Jul 12, 2026
5ae363e
fix(web): retry initial ref page failures
jakeleventhal Jul 12, 2026
fb0a957
fix(web): keep retrying failed ref pages
jakeleventhal Jul 12, 2026
3ffbe06
refactor(server): type missing remote ref failures
jakeleventhal Jul 12, 2026
3f42da8
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 @@ -6280,6 +6280,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 @@ -528,6 +528,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 @@ -542,6 +543,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 @@ -666,6 +671,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 @@ -2143,8 +2143,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 @@ -2153,6 +2155,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function*
Effect.orElseSucceed(() => false),
);
currentPath = exists ? candidatePath : null;
if (!sawMainCheckout) {
mainCheckoutPath = currentPath;
sawMainCheckout = true;
}
} else if (line.startsWith("branch refs/heads/") && currentPath) {
worktreeMap.set(line.slice("branch refs/heads/".length), currentPath);
} else if (line === "") {
Expand All @@ -2163,14 +2169,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 @@ -2236,6 +2243,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 @@ -2321,10 +2329,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