feat(app,server): rank new-workspace picker by recency with debounced search
Plumb updatedAt through GitHub issue/PR search and committerDate through branch suggestions so the new-workspace picker interleaves branches and PRs sorted by last activity. Debounce the search query so remote GitHub searches are driven by what the user typed rather than a static list.
This commit is contained in:
@@ -765,12 +765,21 @@ const x = 1;
|
||||
execSync("git fetch origin", { cwd: repoDir });
|
||||
|
||||
const branches = await listBranchSuggestions(repoDir, { limit: 50 });
|
||||
expect(branches).toContain("main");
|
||||
expect(branches).toContain("local-only");
|
||||
expect(branches).toContain("remote-only");
|
||||
expect(branches.filter((name) => name === "main")).toHaveLength(1);
|
||||
expect(branches).not.toContain("HEAD");
|
||||
expect(branches.some((name) => name.startsWith("origin/"))).toBe(false);
|
||||
const branchNames = branches.map((branch) => branch.name);
|
||||
expect(branchNames).toContain("main");
|
||||
expect(branchNames).toContain("local-only");
|
||||
expect(branchNames).toContain("remote-only");
|
||||
expect(branchNames.filter((name) => name === "main")).toHaveLength(1);
|
||||
expect(branchNames).not.toContain("HEAD");
|
||||
expect(branchNames.some((name) => name.startsWith("origin/"))).toBe(false);
|
||||
expect(branches).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "main",
|
||||
committerDate: expect.any(Number),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("filters branch suggestions by query and enforces result limit", async () => {
|
||||
@@ -786,7 +795,8 @@ const x = 1;
|
||||
limit: 1,
|
||||
});
|
||||
expect(branches).toHaveLength(1);
|
||||
expect(branches[0]?.toLowerCase()).toContain("feature/");
|
||||
expect(branches[0]?.name.toLowerCase()).toContain("feature/");
|
||||
expect(branches[0]?.committerDate).toEqual(expect.any(Number));
|
||||
});
|
||||
|
||||
it("disables GitHub features when gh is unavailable", async () => {
|
||||
@@ -816,6 +826,7 @@ const x = 1;
|
||||
state: "OPEN",
|
||||
body: "issue body",
|
||||
labels: ["bug"],
|
||||
updatedAt: "2026-04-18T12:00:00Z",
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -832,6 +843,7 @@ const x = 1;
|
||||
baseRefName: "main",
|
||||
headRefName: "feature",
|
||||
labels: ["enhancement"],
|
||||
updatedAt: "2026-04-18T13:00:00Z",
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -853,6 +865,7 @@ const x = 1;
|
||||
labels: ["bug"],
|
||||
baseRefName: null,
|
||||
headRefName: null,
|
||||
updatedAt: "2026-04-18T12:00:00Z",
|
||||
},
|
||||
{
|
||||
kind: "pr",
|
||||
@@ -864,6 +877,7 @@ const x = 1;
|
||||
labels: ["enhancement"],
|
||||
baseRefName: "main",
|
||||
headRefName: "feature",
|
||||
updatedAt: "2026-04-18T13:00:00Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -890,6 +904,7 @@ const x = 1;
|
||||
baseRefName: "main",
|
||||
headRefName: "feature",
|
||||
labels: ["enhancement"],
|
||||
updatedAt: "2026-04-18T13:00:00Z",
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -913,6 +928,7 @@ const x = 1;
|
||||
labels: ["enhancement"],
|
||||
baseRefName: "main",
|
||||
headRefName: "feature",
|
||||
updatedAt: "2026-04-18T13:00:00Z",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -128,6 +128,11 @@ interface GitRef {
|
||||
committerDate: number;
|
||||
}
|
||||
|
||||
export interface BranchSuggestion {
|
||||
name: string;
|
||||
committerDate: number;
|
||||
}
|
||||
|
||||
async function listGitRefs(cwd: string, refPrefix: string): Promise<GitRef[]> {
|
||||
const { stdout } = await runGitCommand(
|
||||
[
|
||||
@@ -181,7 +186,7 @@ function sortBranchSuggestions(
|
||||
export async function listBranchSuggestions(
|
||||
cwd: string,
|
||||
options?: { query?: string; limit?: number },
|
||||
): Promise<string[]> {
|
||||
): Promise<BranchSuggestion[]> {
|
||||
await requireGitRepo(cwd);
|
||||
|
||||
const requestedLimit = options?.limit ?? 50;
|
||||
@@ -227,7 +232,10 @@ export async function listBranchSuggestions(
|
||||
}
|
||||
|
||||
const ordered = sortBranchSuggestions(filteredNames, branchMeta, query);
|
||||
return ordered.slice(0, limit);
|
||||
return ordered.slice(0, limit).map((name) => ({
|
||||
name,
|
||||
committerDate: branchMeta.get(name)?.committerDate ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
async function listCheckoutFileChanges(
|
||||
@@ -1936,6 +1944,7 @@ export interface GitHubSearchResult {
|
||||
labels: string[];
|
||||
baseRefName?: string | null;
|
||||
headRefName?: string | null;
|
||||
updatedAt?: string;
|
||||
}>;
|
||||
githubFeaturesEnabled: boolean;
|
||||
}
|
||||
@@ -1997,6 +2006,7 @@ export async function searchGitHubIssuesAndPrs(
|
||||
labels: item.labels,
|
||||
baseRefName: null,
|
||||
headRefName: null,
|
||||
updatedAt: item.updatedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2013,6 +2023,7 @@ export async function searchGitHubIssuesAndPrs(
|
||||
labels: item.labels,
|
||||
baseRefName: item.baseRefName,
|
||||
headRefName: item.headRefName,
|
||||
updatedAt: item.updatedAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user