Phase 4: Move screen selection state into the screens themselves
ToolsScreen, PromptsScreen, and ResourcesScreen each tracked the user's current selection via `selectedXxxName` / `onSelectXxx` props, hoisting screen-local UI state into whatever rendered them. InspectorView never threaded those callbacks anywhere, so clicks in the controls panel did not display the matching detail panel — the detail panel only rendered if a parent fed selection back in. Move selection into `useState` inside each screen and drop the `selectedXxxName` / `onSelectXxx` props. MCP-derived data (`callState`, `getPromptState`, `readState`) keeps flowing in from `InspectorView` as props. ResourcesScreen wraps `onReadResource` so a template-expanded URI becomes the selected resource for preview rendering. Update each screen's stories to drop the deleted props. Stories that need a pre-selected item use a `play` function to click it — no component coupling to Storybook. Add Phase 4.3 to the interfaces plan describing the rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d9b2009804
commit
4c1d2f2c34
2 changed files with 171 additions and 86 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import type { ElicitRequest, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { Modal } from "@mantine/core";
|
||||
import { fn } from "storybook/test";
|
||||
import { fn, userEvent, within } from "storybook/test";
|
||||
import { ToolsScreen } from "./ToolsScreen";
|
||||
import type { ToolCallState } from "./ToolsScreen";
|
||||
import { SamplingRequestPanel } from "../../groups/SamplingRequestPanel/SamplingRequestPanel";
|
||||
|
|
@ -17,7 +17,6 @@ const meta: Meta<typeof ToolsScreen> = {
|
|||
args: {
|
||||
listChanged: false,
|
||||
onRefreshList: fn(),
|
||||
onSelectTool: fn(),
|
||||
onCallTool: fn(),
|
||||
onCancelCall: fn(),
|
||||
onClearResult: fn(),
|
||||
|
|
@ -77,6 +76,11 @@ const resultState: ToolCallState = {
|
|||
},
|
||||
};
|
||||
|
||||
async function selectToolByLabel(canvasElement: HTMLElement, label: string) {
|
||||
const canvas = within(canvasElement);
|
||||
await userEvent.click(await canvas.findByText(label));
|
||||
}
|
||||
|
||||
export const NoSelection: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
|
|
@ -86,16 +90,20 @@ export const NoSelection: Story = {
|
|||
export const ToolSelected: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
selectedToolName: "create_record",
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(canvasElement, "Create Record");
|
||||
},
|
||||
};
|
||||
|
||||
export const WithResult: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
selectedToolName: "create_record",
|
||||
callState: resultState,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(canvasElement, "Create Record");
|
||||
},
|
||||
};
|
||||
|
||||
export const LongToolName: Story = {
|
||||
|
|
@ -130,15 +138,18 @@ export const LongToolName: Story = {
|
|||
},
|
||||
},
|
||||
],
|
||||
selectedToolName:
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(
|
||||
canvasElement,
|
||||
"organization_internal_database_complex_multi_table_join_query_with_aggregation_and_filtering",
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
selectedToolName: "create_record",
|
||||
callState: {
|
||||
status: "error",
|
||||
result: {
|
||||
|
|
@ -152,6 +163,9 @@ export const WithError: Story = {
|
|||
},
|
||||
},
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(canvasElement, "Create Record");
|
||||
},
|
||||
};
|
||||
|
||||
export const WithListChanged: Story = {
|
||||
|
|
@ -176,9 +190,11 @@ const elicitFormRequest = {
|
|||
export const WithSamplingModal: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
selectedToolName: "create_record",
|
||||
callState: { status: "pending" },
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(canvasElement, "Create Record");
|
||||
},
|
||||
render: (args) => (
|
||||
<>
|
||||
<ToolsScreen {...args} />
|
||||
|
|
@ -217,9 +233,11 @@ export const WithSamplingModal: Story = {
|
|||
export const WithElicitationModal: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
selectedToolName: "create_record",
|
||||
callState: { status: "pending" },
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(canvasElement, "Create Record");
|
||||
},
|
||||
render: (args) => (
|
||||
<>
|
||||
<ToolsScreen {...args} />
|
||||
|
|
@ -240,9 +258,11 @@ export const WithElicitationModal: Story = {
|
|||
export const WithPendingRequests: Story = {
|
||||
args: {
|
||||
tools: sampleTools,
|
||||
selectedToolName: "create_record",
|
||||
callState: { status: "pending" },
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
await selectToolByLabel(canvasElement, "Create Record");
|
||||
},
|
||||
render: (args) => (
|
||||
<>
|
||||
<ToolsScreen {...args} />
|
||||
|
|
|
|||
|
|
@ -17,11 +17,9 @@ export interface ToolCallState {
|
|||
|
||||
export interface ToolsScreenProps {
|
||||
tools: Tool[];
|
||||
selectedToolName?: string;
|
||||
callState?: ToolCallState;
|
||||
listChanged: boolean;
|
||||
onRefreshList: () => void;
|
||||
onSelectTool: (name: string) => void;
|
||||
onCallTool: (name: string, args: Record<string, unknown>) => void;
|
||||
onCancelCall?: () => void;
|
||||
onClearResult?: () => void;
|
||||
|
|
@ -58,15 +56,16 @@ const EmptyState = Text.withProps({
|
|||
|
||||
export function ToolsScreen({
|
||||
tools,
|
||||
selectedToolName,
|
||||
callState,
|
||||
listChanged,
|
||||
onRefreshList,
|
||||
onSelectTool,
|
||||
onCallTool,
|
||||
onCancelCall,
|
||||
onClearResult,
|
||||
}: ToolsScreenProps) {
|
||||
const [selectedToolName, setSelectedToolName] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [formValues, setFormValues] = useState<Record<string, unknown>>({});
|
||||
const selectedTool = selectedToolName
|
||||
? tools.find((t) => t.name === selectedToolName)
|
||||
|
|
@ -84,7 +83,7 @@ export function ToolsScreen({
|
|||
onRefreshList={onRefreshList}
|
||||
onSelectTool={(name) => {
|
||||
setFormValues({});
|
||||
onSelectTool(name);
|
||||
setSelectedToolName(name);
|
||||
}}
|
||||
/>
|
||||
</SidebarCard>
|
||||
|
|
|
|||
Loading…
Reference in a new issue