diff --git a/canvas/src/components/tabs/ConfigTab.tsx b/canvas/src/components/tabs/ConfigTab.tsx index fdb8eb38..9447d09a 100644 --- a/canvas/src/components/tabs/ConfigTab.tsx +++ b/canvas/src/components/tabs/ConfigTab.tsx @@ -886,10 +886,24 @@ export function ConfigTab({ workspaceId }: Props) { )} -
- update("skills", v)} placeholder="e.g. code-review" /> - update("tools", v)} placeholder="e.g. web_search, filesystem" /> - update("prompt_files", v)} placeholder="e.g. system-prompt.md" /> + {/* Skills + Tools used to live here as TagList inputs. They were + redundant with their dedicated tabs: + - Skills → managed via SkillsTab (per-workspace skill folders) + - Tools → managed via the Plugins tab (install/uninstall) + Editing them here only set the config.yaml field; the + actual install/load happened elsewhere. Removed to stop + showing the misnamed list-input affordance. */} + +
+

+ Markdown files that compose this workspace's system prompt. + Loaded in order at boot from the workspace config dir + (e.g. system-prompt.md,{' '} + CLAUDE.md,{' '} + AGENTS.md). Edit the file + contents directly via the Files tab. +

+ update("prompt_files", v)} placeholder="e.g. system-prompt.md" />
diff --git a/canvas/src/components/tabs/__tests__/ConfigTab.sections.test.tsx b/canvas/src/components/tabs/__tests__/ConfigTab.sections.test.tsx new file mode 100644 index 00000000..2e083fb3 --- /dev/null +++ b/canvas/src/components/tabs/__tests__/ConfigTab.sections.test.tsx @@ -0,0 +1,125 @@ +// @vitest-environment jsdom +// +// Regression tests for the ConfigTab section restructure (user feedback +// 2026-05-04: "Skills and Tools are having their own tab as plugin, and +// Prompt Files are in the file system which can be directly edited. Am +// I missing something?" + "Tools should be merged into plugin then, and +// for prompt files... should be in another section than in skill& tools"). +// +// What this pins: +// 1. The "Skills & Tools" section title is gone. +// 2. Editable Skills + Tools tag inputs are gone (managed elsewhere). +// 3. A dedicated "Prompt Files" section exists with explanatory text. +// +// If a future PR re-adds the Skills/Tools tag inputs to ConfigTab, this +// suite catches it. + +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, cleanup, waitFor, fireEvent } from "@testing-library/react"; +import React from "react"; + +afterEach(cleanup); + +const apiGet = vi.fn(); +vi.mock("@/lib/api", () => ({ + api: { + get: (path: string) => apiGet(path), + patch: vi.fn(), + put: vi.fn(), + post: vi.fn(), + del: vi.fn(), + }, +})); + +const storeUpdateNodeData = vi.fn(); +const storeRestartWorkspace = vi.fn(); +vi.mock("@/store/canvas", () => ({ + useCanvasStore: Object.assign( + (selector: (s: unknown) => unknown) => + selector({ restartWorkspace: storeRestartWorkspace, updateNodeData: storeUpdateNodeData }), + { + getState: () => ({ + restartWorkspace: storeRestartWorkspace, + updateNodeData: storeUpdateNodeData, + }), + }, + ), +})); + +vi.mock("../AgentCardSection", () => ({ + AgentCardSection: () =>
, +})); + +import { ConfigTab } from "../ConfigTab"; + +beforeEach(() => { + apiGet.mockReset(); + apiGet.mockImplementation((path: string) => { + if (path === `/workspaces/ws-test`) { + return Promise.resolve({ runtime: "claude-code" }); + } + if (path === `/workspaces/ws-test/model`) { + return Promise.resolve({ model: "claude-opus-4-7" }); + } + if (path === `/workspaces/ws-test/provider`) { + return Promise.resolve({ provider: "anthropic-oauth", source: "default" }); + } + if (path === `/workspaces/ws-test/files/config.yaml`) { + return Promise.resolve({ content: "name: test\nruntime: claude-code\n" }); + } + if (path === "/templates") { + return Promise.resolve([ + { id: "claude-code", name: "Claude Code", runtime: "claude-code", providers: [] }, + ]); + } + return Promise.reject(new Error(`unmocked api.get: ${path}`)); + }); +}); + +describe("ConfigTab section restructure", () => { + it("does not render a 'Skills & Tools' section title", async () => { + render(); + await waitFor(() => expect(apiGet).toHaveBeenCalled()); + // Section button uses the title as its accessible name; should be absent. + expect(screen.queryByRole("button", { name: /Skills\s*&\s*Tools/i })).toBeNull(); + }); + + it("does not render an editable Skills tag input", async () => { + render(); + await waitFor(() => expect(apiGet).toHaveBeenCalled()); + // TagList renders its label; check no input labelled "Skills" in the form. + // (Skills are managed via the dedicated Skills tab.) + const skillsLabels = screen + .queryAllByText(/^Skills$/) + .filter((el) => el.tagName.toLowerCase() === "label"); + expect(skillsLabels).toHaveLength(0); + }); + + it("does not render an editable Tools tag input", async () => { + render(); + await waitFor(() => expect(apiGet).toHaveBeenCalled()); + // Tools are managed via the Plugins tab — install a plugin → its tools + // become available. No reason to type tool names here. + const toolsLabels = screen + .queryAllByText(/^Tools$/) + .filter((el) => el.tagName.toLowerCase() === "label"); + expect(toolsLabels).toHaveLength(0); + }); + + it("renders a dedicated 'Prompt Files' section with explanatory copy", async () => { + render(); + await waitFor(() => expect(apiGet).toHaveBeenCalled()); + // Section is collapsed by default — find + expand first. + const sectionButton = screen.getByRole("button", { name: /Prompt Files/i }); + expect(sectionButton).toBeTruthy(); + fireEvent.click(sectionButton); + // Explanatory copy mentions system-prompt.md (split across tags + // so use textContent on any element rather than the default text matcher). + await waitFor(() => { + const matches = screen.queryAllByText((_, el) => + (el?.textContent || "").includes("system-prompt.md"), + ); + expect(matches.length).toBeGreaterThan(0); + }); + }); +});