Phase 5 audit: annotate spec, close HistoryControls partial

Audited every component under clients/web/src/components/ against its
v2_ux_interfaces.md entry. Added an "Audit results" section near the top
of the spec with the verified count (57/62 satisfied, 5 partial) and the
specific gaps still open.

While auditing, closed the HistoryControls gap directly:

- Added RequestMethod union type to core/mcp/types.ts, derived from the
  SDK's Client/Server Request and Notification schemas plus a synthetic
  "response" entry for result/error MessageEntries.
- Typed methodFilter and onMethodFilterChange in HistoryControls,
  HistoryListPanel, historyUtils.extractMethod with RequestMethod.
- Lifted the hardcoded METHOD_OPTIONS list out of HistoryControls into
  a required availableMethods prop. HistoryScreen now derives it from
  the live entries via useMemo + extractMethod.
- Updated HistoryControls.stories.tsx to pass availableMethods.

The remaining 5 partials (ElicitationUrlPanel, InlineElicitationRequest,
InlineSamplingRequest, ExperimentalFeaturesPanel, ResourceListItem) are
either blocked on the v2 core hook layer effort or call out follow-up
work that has its own design questions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-04-27 09:51:02 -04:00
commit dde43ae47c
7 changed files with 104 additions and 24 deletions

View file

@ -2,11 +2,23 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test";
import { HistoryControls } from "./HistoryControls";
const SAMPLE_METHODS = [
"tools/call",
"tools/list",
"resources/read",
"resources/list",
"prompts/get",
"prompts/list",
"sampling/createMessage",
"elicitation/create",
] as const;
const meta: Meta<typeof HistoryControls> = {
title: "Groups/HistoryControls",
component: HistoryControls,
args: {
searchText: "",
availableMethods: [...SAMPLE_METHODS],
onSearchChange: fn(),
onMethodFilterChange: fn(),
},

View file

@ -1,26 +1,18 @@
import { Select, Stack, TextInput, Title } from "@mantine/core";
const METHOD_OPTIONS = [
"tools/call",
"tools/list",
"resources/read",
"resources/list",
"prompts/get",
"prompts/list",
"sampling/createMessage",
"elicitation/create",
];
import type { RequestMethod } from "@inspector/core/mcp/types.js";
export interface HistoryControlsProps {
searchText: string;
methodFilter?: string;
methodFilter?: RequestMethod;
availableMethods: RequestMethod[];
onSearchChange: (text: string) => void;
onMethodFilterChange: (method: string | undefined) => void;
onMethodFilterChange: (method: RequestMethod | undefined) => void;
}
export function HistoryControls({
searchText,
methodFilter,
availableMethods,
onSearchChange,
onMethodFilterChange,
}: HistoryControlsProps) {
@ -36,9 +28,11 @@ export function HistoryControls({
<Title order={6}>Filter by Method</Title>
<Select
placeholder="All methods"
data={METHOD_OPTIONS}
data={availableMethods}
value={methodFilter ?? null}
onChange={(value) => onMethodFilterChange(value ?? undefined)}
onChange={(value) =>
onMethodFilterChange((value as RequestMethod | null) ?? undefined)
}
clearable
/>
</Stack>

View file

@ -8,7 +8,10 @@ import {
Text,
Title,
} from "@mantine/core";
import type { MessageEntry } from "../../../../../../core/mcp/types.js";
import type {
MessageEntry,
RequestMethod,
} from "../../../../../../core/mcp/types.js";
import { HistoryEntry } from "../HistoryEntry/HistoryEntry";
import { ListToggle } from "../../elements/ListToggle/ListToggle";
import { extractMethod } from "../historyUtils.js";
@ -17,7 +20,7 @@ export interface HistoryListPanelProps {
entries: MessageEntry[];
pinnedIds: Set<string>;
searchText: string;
methodFilter?: string;
methodFilter?: RequestMethod;
onClearAll: () => void;
onExport: () => void;
onReplay: (id: string) => void;
@ -53,7 +56,7 @@ function formatHistoryTitle(count: number): string {
function matchesFilters(
entry: MessageEntry,
searchText: string,
methodFilter?: string,
methodFilter?: RequestMethod,
): boolean {
const method = extractMethod(entry);
if (methodFilter && method !== methodFilter) return false;

View file

@ -1,8 +1,11 @@
import type { MessageEntry } from "../../../../../core/mcp/types.js";
import type {
MessageEntry,
RequestMethod,
} from "../../../../../core/mcp/types.js";
export function extractMethod(entry: MessageEntry): string {
export function extractMethod(entry: MessageEntry): RequestMethod {
if ("method" in entry.message) {
return entry.message.method;
return entry.message.method as RequestMethod;
}
return "response";
}

View file

@ -1,8 +1,12 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import { Card, Flex, Stack } from "@mantine/core";
import type { MessageEntry } from "../../../../../../core/mcp/types.js";
import type {
MessageEntry,
RequestMethod,
} from "../../../../../../core/mcp/types.js";
import { HistoryControls } from "../../groups/HistoryControls/HistoryControls";
import { HistoryListPanel } from "../../groups/HistoryListPanel/HistoryListPanel.js";
import { extractMethod } from "../../groups/historyUtils.js";
export interface HistoryScreenProps {
entries: MessageEntry[];
@ -39,7 +43,12 @@ export function HistoryScreen({
onTogglePin,
}: HistoryScreenProps) {
const [searchText, setSearchText] = useState("");
const [methodFilter, setMethodFilter] = useState<string | undefined>();
const [methodFilter, setMethodFilter] = useState<RequestMethod | undefined>();
const availableMethods = useMemo(
() => Array.from(new Set(entries.map(extractMethod))).sort(),
[entries],
);
return (
<ScreenLayout>
@ -48,6 +57,7 @@ export function HistoryScreen({
<HistoryControls
searchText={searchText}
methodFilter={methodFilter}
availableMethods={availableMethods}
onSearchChange={setSearchText}
onMethodFilterChange={setMethodFilter}
/>

View file

@ -1,5 +1,7 @@
import type {
CallToolResult,
ClientNotification,
ClientRequest,
GetPromptResult,
Implementation,
JSONRPCErrorResponse,
@ -10,6 +12,8 @@ import type {
ReadResourceResult,
Resource,
ServerCapabilities,
ServerNotification,
ServerRequest,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import type { JsonValue } from "../json/jsonUtils.js";
@ -98,6 +102,14 @@ export interface MessageEntry {
duration?: number; // Time between request and response in ms
}
/** Method name for any MessageEntry traffic, plus synthetic "response" for result/error entries. */
export type RequestMethod =
| ClientRequest["method"]
| ClientNotification["method"]
| ServerRequest["method"]
| ServerNotification["method"]
| "response";
export type FetchRequestCategory = "auth" | "transport";
export interface FetchRequestEntry {

View file

@ -94,6 +94,52 @@ For each component, the plan records:
---
## Audit results (Phase 5 close-out, 2026-04-26)
After Phases 0–5 of [`v2_ux_interfaces_plan.md`](v2_ux_interfaces_plan.md), the
component layer was audited against this spec by reading every component file
under `clients/web/src/components/` and comparing its exported props interface
and internal rendering against the corresponding entry's **Target props** and
**Internal refactors** here. Spec entries that reference `Inspector*`
placeholder names map to v1.5 actual names per the plan's
"Name-mapping correction" table — components using those v1.5 names
(`MCPServerConfig`, `ServerType`, `Task`, `MessageEntry`, etc.) are treated
as satisfied.
**Result: 57 of 62 satisfied, 5 partial, 0 unstarted.**
### Partials still open
- **ElicitationUrlPanel** — accepts scalar `message` / `url` / `requestId`
props. Spec target wraps these in `InspectorUrlElicitRequest`. **Blocked
on the v2 core hook layer effort** — the wrapper type was deferred from
Phase 0.2 and was not added to `core/mcp/types.ts` because URL-mode
elicitation has no dedicated handler in this repo yet.
- **InlineElicitationRequest** — accepts only `ElicitRequest['params']`
(form variant). Spec target is a discriminated union over
`ElicitRequest | InspectorUrlElicitRequest`. **Same blocker as above.**
- **InlineSamplingRequest** — exposes a flat `responseText: string` prop.
Spec target is `draftResult?: CreateMessageResult`. In-scope to fix
without the hook layer; deferred to a follow-up because the inline card's
draft-editing UX is co-evolving with `SamplingRequestPanel`.
- **ExperimentalFeaturesPanel** — accepts a local
`clientToggles: ClientExperimentalToggle[]` prop. Spec target is the
freeform `clientExperimental: ClientCapabilities['experimental']` record
iterated in-component. The local-toggle shape was retained because the
panel needs per-toggle metadata (label, description) that the bare
capabilities record doesn't carry; reconciling the two shapes is a
follow-up.
- **ResourceListItem** — props match the target shape, but the entry's
internal-refactor bullet ("Actually render the annotations currently
received but unused") is unimplemented; the component still ignores
`resource.annotations`.
The component contracts produced by this refactor are the input spec for
the upcoming v2 `core/` hook layer effort. The two blocked partials above
will close out as part of that work.
---
## Section 1 — Elements (`clients/web/src/components/elements/`)
> Small, focused presentational primitives. Each renders a single MCP concept