fix(web): collapsible tool description in ToolDetailPanel — shown by default (#1381) (#1459)

* fix(web): collapse long tool descriptions in ToolDetailPanel (#1381)

Long tool descriptions (e.g. sequential-thinking-server's "Sequential
Thinking" tool) filled the panel and pushed the form + Execute button
off-screen. The internal scroll region for the form/progress was already
in place; this adds the collapsible description.

- Wrap the description in a Mantine Collapse, collapsed by default.
- Expose the trigger as a chevron ActionIcon pinned to the right of the
  tool-name row (FaChevronRight collapsed / FaChevronDown expanded), with
  Show/Hide description aria-labels. Rendered only when a description exists.
- Reset to collapsed on tool change via the adjust-state-during-render
  pattern, mirroring how ToolsScreen clears formValues.

Tests + stories cover collapsed default, expand/collapse toggle, reset on
tool switch, and the no-description case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(web): show tool description by default, click to hide (#1381)

Most tool descriptions are short, so default to showing the description
and let the user collapse a long one via the chevron, rather than the
reverse. Flips the initial/reset state to open and updates the toggle
aria-labels, tests, and stories accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(web): use Ri disclosure chevrons in ToolDetailPanel (#1459 review)

Swap react-icons/fa FaChevronDown/FaChevronRight for the established
react-icons/ri RiArrowDownSLine/RiArrowRightSLine disclosure-chevron
convention already used in ResourceLink, keeping chevron visuals
consistent across the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* a11y(web): wire description toggle as expandable control (#1459 review)

Add aria-expanded + aria-controls to the description chevron, tying it to
the Collapse region (via useId) so assistive tech announces it as a single
expandable control rather than two differently-labelled buttons. Adds a
test covering the wiring and the aria-expanded flip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-06-13 19:13:29 -04:00 • committed by GitHub
commit 92da6c1675
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { fn } from "storybook/test";
import { expect, fn, userEvent, within } from "storybook/test";
import { ToolDetailPanel } from "./ToolDetailPanel";
const meta: Meta<typeof ToolDetailPanel> = {
@ -105,12 +105,70 @@ const batchProcessTool: Tool = {
},
};
// Mirrors the gnarly `sequential-thinking-server` case from issue #1381: a very
// long description that pushes the form and Execute footer down. Descriptions
// show by default; the chevron lets the user hide a long one to reclaim space.
const longDescriptionTool: Tool = {
name: "sequentialthinking",
title: "Sequential Thinking",
description: [
"A detailed tool for dynamic and reflective problem-solving through thoughts.",
"This tool helps analyze problems through a flexible thinking process that can",
"adapt and evolve. Each thought can build on, question, or revise previous",
"insights as understanding deepens.",
"",
"When to use this tool: breaking down complex problems into steps, planning and",
"design with room for revision, analysis that might need course correction, and",
"problems where the full scope might not be clear initially.",
].join(" "),
inputSchema: {
type: "object",
properties: {
thought: { type: "string", description: "Your current thinking step" },
nextThoughtNeeded: {
type: "boolean",
description: "Whether another thought step is needed",
},
},
required: ["thought", "nextThoughtNeeded"],
},
};
export const SimpleStringParam: Story = {
args: {
tool: sendMessageTool,
},
};
// Long description shown by default — the chevron offers to hide it.
export const LongDescription: Story = {
args: {
tool: longDescriptionTool,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
await canvas.findByRole("button", { name: "Hide description" }),
).toBeInTheDocument();
},
};
// Clicking the chevron hides the description (toggle flips to "Show").
export const LongDescriptionHidden: Story = {
args: {
tool: longDescriptionTool,
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(
await canvas.findByRole("button", { name: "Hide description" }),
);
await expect(
await canvas.findByRole("button", { name: "Show description" }),
).toBeInTheDocument();
},
};
export const MultipleParams: Story = {
args: {
tool: createRecordTool,