Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 13 additions & 14 deletions packages/extension/src/sidebar_tree_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,26 +73,25 @@ export class SidebarTreeService {

if (projectType === "environment") {
// Collect environment workspace folders (#895)
// Always overwrite: workspace source wins dedup over resolved (#895 bugfix)
if (this.deps.readEnvironmentToml) {
try {
const envManifest = this.deps.readEnvironmentToml(dir);
if (envManifest) {
const slug = envManifest.slug;
if (!envBySlug.has(slug)) {
envBySlug.set(slug, {
path: dir,
envBySlug.set(slug, {
path: dir,
name: envManifest.name,
projectType: "environment",
source: "workspace",
boundProjectCount: 0,
environment: {
name: envManifest.name,
projectType: "environment",
source: "workspace",
boundProjectCount: 0,
environment: {
name: envManifest.name,
slug,
path: dir,
colorIndex: envColorIndex(slug),
},
});
}
slug,
path: dir,
colorIndex: envColorIndex(slug),
},
});
}
} catch {
// Manifest read failure → skip this environment
Expand Down
35 changes: 33 additions & 2 deletions packages/extension/test/sidebar_env_tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ describe("SidebarTreeService environment discovery (#895)", () => {
expect(envRoots[0].environment?.slug).toBe("spin-qubit");
});

it("two workspace folders with same slug — first in order wins", () => {
it("two workspace folders with same slug — last in order wins (both workspace-sourced)", () => {
const service = makeDiscoveryService({
folders: [
{ path: "/env/first", name: "first" },
Expand All @@ -223,7 +223,8 @@ describe("SidebarTreeService environment discovery (#895)", () => {
const roots = service.getRoots();
const envRoots = roots.filter((r) => r.projectType === "environment");
expect(envRoots.length).toBe(1);
expect(envRoots[0].path).toBe("/env/first");
expect(envRoots[0].path).toBe("/env/second");
expect(envRoots[0].source).toBe("workspace");
});

it("boundProjectCount is 0 when no projects bind to the environment", () => {
Expand Down Expand Up @@ -270,4 +271,34 @@ describe("SidebarTreeService environment discovery (#895)", () => {
expect(roots[1].projectType).toBe("research");
expect(roots[2].projectType).toBe("dev");
});

it("workspace source wins dedup even when project appears before environment in folder order", () => {
// This is the root cause of the "Remove from Workspace" bug:
// if a research project is iterated before the environment workspace folder,
// the project resolution adds the environment with source "resolved" first,
// and the workspace folder must overwrite it.
const service = makeDiscoveryService({
folders: [
// Project comes FIRST — its resolution will try to add the env as "resolved"
{ path: "/proj/a", name: "a" },
// Environment workspace folder comes SECOND — must still win the dedup
{ path: "/env/shared", name: "shared" },
],
projectTypes: {
"/proj/a": "research",
"/env/shared": "environment",
},
toml: { "/proj/a": { name: "Project A" } },
envToml: { "/env/shared": { name: "Shared Env", slug: "shared" } },
envResolution: {
"/proj/a": { path: "/env/shared", slug: "shared", name: "Shared Env" },
},
});

const roots = service.getRoots();
const envRoots = roots.filter((r) => r.projectType === "environment");
expect(envRoots).toHaveLength(1);
expect(envRoots[0].source).toBe("workspace");
expect(envRoots[0].path).toBe("/env/shared");
});
});
Loading