From beab899501db790968891eecf77414c34b5f4e00 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Mon, 4 May 2026 20:02:05 -0700 Subject: [PATCH] feat(ConfigTab): drop Skills/Tools tag inputs, give Prompt Files its own section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback (2026-05-04 conversation): > "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... it > should be in another section than in skill& tools" The "Skills & Tools" section in ConfigTab had three TagList inputs: - Skills: managed via the dedicated SkillsTab (per-workspace skill folders) — duplicate UI affordance - Tools: managed via the Plugins tab (install a plugin → its tools become available) — duplicate UI affordance - Prompt Files: load order for system-prompt files — semantically unrelated to skills/tools Drop the Skills + Tools inputs. Move Prompt Files into its own section with explanatory copy that names the auto-loaded files (system-prompt.md, CLAUDE.md, AGENTS.md) and points users at the Files tab for actual editing. Schema fields `config.skills` and `config.tools` are KEPT (load-bearing for runtime skill loading + tool registry); only the inline editor goes away. Operators who need to edit them can still use the Raw YAML toggle. Tests: - New ConfigTab.sections.test.tsx with 4 cases: 1. "Skills & Tools" section title is gone 2. Skills tag input is absent 3. Tools tag input is absent 4. Prompt Files section exists with explanatory copy Sibling ConfigTab tests (hermes, provider) all still pass (20/20). Co-Authored-By: Claude Opus 4.7 (1M context) --- canvas/src/components/tabs/ConfigTab.tsx | 22 ++- .../__tests__/ConfigTab.sections.test.tsx | 125 ++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 canvas/src/components/tabs/__tests__/ConfigTab.sections.test.tsx 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); + }); + }); +});