feat: add relay reconnect with grace period and branch suggestions

This commit is contained in:
Mohamed Boudra
2026-02-13 09:46:40 +07:00
parent 837d0fb387
commit bf432b3d3a
23 changed files with 1137 additions and 103 deletions
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { buildBranchComboOptions, normalizeBranchOptionName } from "./branch-suggestions";
describe("normalizeBranchOptionName", () => {
it("normalizes local and origin-prefixed refs", () => {
expect(normalizeBranchOptionName("refs/heads/main")).toBe("main");
expect(normalizeBranchOptionName("refs/remotes/origin/main")).toBe("main");
expect(normalizeBranchOptionName("origin/feature/test")).toBe("feature/test");
expect(normalizeBranchOptionName("feature/test")).toBe("feature/test");
});
it("filters out empty values and HEAD", () => {
expect(normalizeBranchOptionName("")).toBeNull();
expect(normalizeBranchOptionName(" ")).toBeNull();
expect(normalizeBranchOptionName("HEAD")).toBeNull();
expect(normalizeBranchOptionName("origin/HEAD")).toBeNull();
});
});
describe("buildBranchComboOptions", () => {
it("merges branch sources and de-duplicates normalized names", () => {
const options = buildBranchComboOptions({
suggestedBranches: ["origin/main", "refs/remotes/origin/main", "feature/a"],
currentBranch: "refs/heads/feature/a",
baseRef: "origin/main",
typedBaseBranch: "main",
worktreeBranchLabels: ["refs/heads/release/next"],
});
expect(options).toEqual([
{ id: "main", label: "main" },
{ id: "feature/a", label: "feature/a" },
{ id: "release/next", label: "release/next" },
]);
});
});
@@ -0,0 +1,51 @@
export type BranchComboOption = {
id: string;
label: string;
};
export function normalizeBranchOptionName(input: string | null | undefined): string | null {
const trimmed = input?.trim();
if (!trimmed || trimmed === "HEAD") {
return null;
}
let normalized = trimmed;
if (normalized.startsWith("refs/heads/")) {
normalized = normalized.slice("refs/heads/".length);
} else if (normalized.startsWith("refs/remotes/")) {
normalized = normalized.slice("refs/remotes/".length);
}
if (normalized.startsWith("origin/")) {
normalized = normalized.slice("origin/".length);
}
return normalized.length > 0 && normalized !== "HEAD" ? normalized : null;
}
export function buildBranchComboOptions(input: {
suggestedBranches?: string[];
currentBranch?: string | null;
baseRef?: string | null;
typedBaseBranch?: string | null;
worktreeBranchLabels?: string[];
}): BranchComboOption[] {
const branchSet = new Set<string>();
const addBranch = (name: string | null | undefined) => {
const normalized = normalizeBranchOptionName(name);
if (normalized) {
branchSet.add(normalized);
}
};
for (const branch of input.suggestedBranches ?? []) {
addBranch(branch);
}
addBranch(input.currentBranch ?? null);
addBranch(input.baseRef ?? null);
addBranch(input.typedBaseBranch ?? null);
for (const label of input.worktreeBranchLabels ?? []) {
addBranch(label);
}
return Array.from(branchSet).map((name) => ({ id: name, label: name }));
}