merge: integrate main into dev

Resolve 9 merge conflicts preserving both sides' intent:
- nix hash, bootstrap hostnames rename, websocket-server wiring
- session.ts handler dedup, snapshot fallback, worktree registration
- worktree-session git service + script/setup features
- sidebar-workspace-list setup navigation, session-context cleanup
- workspace-registry-bootstrap test kept (code still exists)
- checkout-git test additions from both sides

Post-merge fixes: isPaneFocused prop rename, server type rebuild.
This commit is contained in:
Mohamed Boudra
2026-04-15 16:07:39 +07:00
73 changed files with 2288 additions and 1126 deletions
+23 -8
View File
@@ -20,11 +20,11 @@ import {
__setPullRequestStatusCacheTtlForTests,
commitAll,
getCachedCheckoutShortstat,
getCurrentBranch,
getCheckoutDiff,
getCheckoutShortstat,
getPullRequestStatus,
getCheckoutStatus,
getCheckoutStatusLite,
listBranchSuggestions,
mergeToBase,
mergeFromBase,
@@ -91,6 +91,15 @@ describe("checkout git utilities", () => {
);
});
it("returns null for getCurrentBranch in a repo with no commits", async () => {
const emptyRepo = join(tempDir, "empty-repo");
execSync(`mkdir -p ${emptyRepo}`);
execSync("git init -b main", { cwd: emptyRepo });
const branch = await getCurrentBranch(emptyRepo);
expect(branch).toBeNull();
});
it("handles status/diff/commit in a normal repo", async () => {
writeFileSync(join(repoDir, "file.txt"), "updated\n");
@@ -170,13 +179,16 @@ const x = 1;
expect(removedLine?.tokens).toEqual([{ text: "old comment line", style: "comment" }]);
});
it("returns lightweight checkout status for normal repos", async () => {
const status = await getCheckoutStatusLite(repoDir);
it("returns checkout root metadata for normal repos", async () => {
const status = await getCheckoutStatus(repoDir);
expect(status.isGit).toBe(true);
if (!status.isGit) {
return;
}
expect(status.currentBranch).toBe("main");
expect(status.worktreeRoot).toBe(repoDir);
expect(status.repoRoot).toBe(repoDir);
expect(status.isPaseoOwnedWorktree).toBe(false);
expect(status.mainRepoRoot).toBeNull();
expect(status.mainRepoRoot ?? null).toBeNull();
});
it("exposes hasRemote when origin is configured", async () => {
@@ -404,7 +416,7 @@ const x = 1;
expect(message).toBe("worktree update");
});
it("returns lightweight checkout status for .paseo worktrees", async () => {
it("returns checkout root metadata for .paseo worktrees", async () => {
const result = await createWorktree({
branchName: "main",
cwd: repoDir,
@@ -413,9 +425,12 @@ const x = 1;
paseoHome,
});
const status = await getCheckoutStatusLite(result.worktreePath, { paseoHome });
const status = await getCheckoutStatus(result.worktreePath, { paseoHome });
expect(status.isGit).toBe(true);
expect(status.worktreeRoot).toBe(result.worktreePath);
if (!status.isGit) {
return;
}
expect(status.repoRoot).toBe(result.worktreePath);
expect(status.isPaseoOwnedWorktree).toBe(true);
expect(status.mainRepoRoot).toBe(repoDir);
});
+10 -75
View File
@@ -516,38 +516,6 @@ export type CheckoutStatusGit = CheckoutStatusGitNonPaseo | CheckoutStatusGitPas
export type CheckoutStatusResult = CheckoutStatus | CheckoutStatusGit;
export type CheckoutStatusLiteNotGit = {
isGit: false;
currentBranch: null;
remoteUrl: null;
worktreeRoot: null;
isPaseoOwnedWorktree: false;
mainRepoRoot: null;
};
export type CheckoutStatusLiteGitNonPaseo = {
isGit: true;
currentBranch: string | null;
remoteUrl: string | null;
worktreeRoot: string;
isPaseoOwnedWorktree: false;
mainRepoRoot: null;
};
export type CheckoutStatusLiteGitPaseo = {
isGit: true;
currentBranch: string | null;
remoteUrl: string | null;
worktreeRoot: string;
isPaseoOwnedWorktree: true;
mainRepoRoot: string;
};
export type CheckoutStatusLiteResult =
| CheckoutStatusLiteNotGit
| CheckoutStatusLiteGitNonPaseo
| CheckoutStatusLiteGitPaseo;
export interface CheckoutDiffResult {
diff: string;
structured?: ParsedDiffFile[];
@@ -591,12 +559,16 @@ async function requireGitRepo(cwd: string): Promise<void> {
}
export async function getCurrentBranch(cwd: string): Promise<string | null> {
const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], {
cwd,
env: READ_ONLY_GIT_ENV,
});
const branch = stdout.trim();
return branch.length > 0 ? branch : null;
try {
const { stdout } = await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], {
cwd,
env: READ_ONLY_GIT_ENV,
});
const branch = stdout.trim();
return branch.length > 0 ? branch : null;
} catch {
return null;
}
}
async function getWorktreeRoot(cwd: string): Promise<string | null> {
@@ -1190,43 +1162,6 @@ export async function getCheckoutStatus(
};
}
export async function getCheckoutStatusLite(
cwd: string,
context?: CheckoutContext,
): Promise<CheckoutStatusLiteResult> {
const inspected = await inspectCheckoutContext(cwd, context);
if (!inspected) {
return {
isGit: false,
currentBranch: null,
remoteUrl: null,
worktreeRoot: null,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
};
}
if (inspected.configured.isPaseoOwnedWorktree) {
return {
isGit: true,
currentBranch: inspected.currentBranch,
remoteUrl: inspected.remoteUrl,
worktreeRoot: inspected.worktreeRoot,
isPaseoOwnedWorktree: true,
mainRepoRoot: await getMainRepoRoot(cwd),
};
}
return {
isGit: true,
currentBranch: inspected.currentBranch,
remoteUrl: inspected.remoteUrl,
worktreeRoot: inspected.worktreeRoot,
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
};
}
export interface CheckoutShortstat {
additions: number;
deletions: number;