fix(project-structure): list only configured projects as dependency candidates (#2572)

The Dependencies checklist offered every recently opened project as a candidate, which made the list long and noisy. A standalone project now lists only its configured dependency projects; in a workspace the other roots attached to the window stay candidates, and Add Project… picks a new one (the empty-state note says so).

Co-authored-by: muutot <92625353+muutot@users.noreply.github.com>
This commit is contained in:
Muu
2026-11-11 01:41:48 +08:00
committed by GitHub
co-authored by muutot
parent 5bdf4835b0
commit eab165b4e0
4 changed files with 12 additions and 42 deletions
@@ -30,18 +30,8 @@ describe("project dependencies", () => {
).toEqual(["/shared", "/deps/lib"]);
});
it("builds the checklist of one root", () => {
const rows = dependencyCandidates("/p/root", ["/deps/lib"], {
recents: [
{ path: "/w/other", name: "Other" },
{ path: "/p/root", name: "Root" },
{ path: "/p/root/sub", name: "Sub" },
{ path: "/deps/lib", name: "lib" },
],
});
expect(rows).toEqual([
{ path: "/deps/lib", name: "lib", checked: true, disabled: false },
{ path: "/w/other", name: "Other", checked: false, disabled: false },
]);
it("builds the checklist of one root from its configured projects", () => {
const rows = dependencyCandidates("/p/root", ["/deps/lib", "/deps/lib", "/p/root", "/p/root/sub"]);
expect(rows).toEqual([{ path: "/deps/lib", name: "lib", checked: true, disabled: false }]);
});
});
@@ -25,7 +25,7 @@ import {
import { SettingsFormContext } from "@/workbench/contributions";
import { call, commands, isTauri, nextRequestId, type FileEntry } from "@/platform/ipc";
import { mergeLibraryGroups } from "@/platform/libraries";
import type { BuildModel, LibraryGroup, RecentProject, WebArtifact } from "@/bindings";
import type { BuildModel, LibraryGroup, WebArtifact } from "@/bindings";
import { relativePath, useProject } from "@/platform/project";
import { PageContext, useSettingsFormFor } from "../components/context";
import {
@@ -606,13 +606,10 @@ export interface DependencyCandidate {
/**
* Rows of the "project dependencies" checklist of a single root (the project folder, or a
* workspace member): the configured dependencies first, then recent projects as candidates.
* workspace member): the configured dependencies. A workspace additionally lists the other
* roots attached to the window, in MemberDependencies.
*/
export function dependencyCandidates(
root: string | null,
dependencies: readonly string[],
options: { recents?: ReadonlyArray<{ path: string; name: string }> } = {},
): DependencyCandidate[] {
export function dependencyCandidates(root: string | null, dependencies: readonly string[]): DependencyCandidate[] {
const out: DependencyCandidate[] = [];
const listed = new Set<string>();
const nameOf = (p: string) => p.slice(p.lastIndexOf("/") + 1).replace(/[\\/]+$/, "") || p;
@@ -622,11 +619,6 @@ export function dependencyCandidates(
listed.add(p);
out.push({ path: p, name: nameOf(p), checked: true, disabled: false });
}
for (const r of options.recents ?? []) {
if (listed.has(r.path) || !attachable(r.path)) continue;
listed.add(r.path);
out.push({ path: r.path, name: r.name || nameOf(r.path), checked: false, disabled: false });
}
return out;
}
@@ -641,21 +633,9 @@ function ModuleDependenciesTab() {
function ProjectDependencies() {
const root = useProject((s) => s.project?.root ?? null);
const [deps, setDeps] = useProjectSetting<string[] | undefined>("projectStructure.dependencies");
const [recents, setRecents] = useState<RecentProject[]>([]);
useEffect(() => {
let live = true;
void commands
.recentProjects()
.then((rs) => live && setRecents(rs))
.catch(() => {});
return () => {
live = false;
};
}, []);
const list = deps ?? [];
const candidates = useMemo(() => dependencyCandidates(root, deps ?? [], { recents }), [root, deps, recents]);
// Only the configured projects: candidates come from Add Project…, not every recent project.
const candidates = useMemo(() => dependencyCandidates(root, deps ?? []), [root, deps]);
const toggle = (path: string, checked: boolean) =>
setDeps(checked ? [...list, path] : list.filter((x) => x !== path));
@@ -680,7 +660,7 @@ function ProjectDependencies() {
<div className={styles.entryList} role="group" aria-label={tx("The project depends on these projects")}>
{candidates.length === 0 && (
<div className={styles.note}>
<T>No candidate projects. Recently opened projects appear here.</T>
<T>No candidate projects yet. Use Add Project… to pick one.</T>
</div>
)}
{candidates.map((c) => (
+1 -1
View File
@@ -883,7 +883,7 @@
"The project depends on these projects": "The project depends on these projects",
"The project depends on {name}": "The project depends on {name}",
"Dependency projects are indexed and navigable together with this project: Go to Declaration, Find Usages and the Project view include them.": "Dependency projects are indexed and navigable together with this project: Go to Declaration, Find Usages and the Project view include them.",
"No candidate projects. Recently opened projects appear here.": "No candidate projects. Recently opened projects appear here.",
"No candidate projects yet. Use Add Project… to pick one.": "No candidate projects yet. Use Add Project… to pick one.",
"Roots": "Roots",
"Dependencies of {name}": "Dependencies of {name}",
"The root depends on these projects": "The root depends on these projects",
+1 -1
View File
@@ -7215,7 +7215,7 @@
"The project depends on these projects": "项目取决于这些项目",
"The project depends on {name}": "项目取决于 {name}",
"Dependency projects are indexed and navigable together with this project: Go to Declaration, Find Usages and the Project view include them.": "依赖项目将与本项目一起建立索引并参与导航:“转到声明”、查找用法和项目视图都会包含它们。",
"No candidate projects. Recently opened projects appear here.": "暂无候选项目。最近打开的项目会显示在这里。",
"No candidate projects yet. Use Add Project… to pick one.": "暂无候选项目。使用「添加项目…」选择。",
"Add Project…": "添加项目…",
"Roots": "根",
"Dependencies of {name}": "{name} 的依赖项",