diff --git a/clients/web/src/App.test.tsx b/clients/web/src/App.test.tsx index a508512f..bf2bdef0 100644 --- a/clients/web/src/App.test.tsx +++ b/clients/web/src/App.test.tsx @@ -309,10 +309,10 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({ onClearCompletedTasks: () => void; onRefreshTasks: () => void; onServerSettings: (id: string) => void; - onClearHistory: () => void; - onReplayHistory: (id: string) => void; - onTogglePinHistory: (id: string) => void; - pinnedHistoryIds?: Set; + onClearProtocol: () => void; + onReplayProtocol: (id: string) => void; + onTogglePinProtocol: (id: string) => void; + pinnedProtocolIds?: Set; }) => (
@@ -419,15 +419,15 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({ - {Array.from(props.pinnedHistoryIds ?? []).join(",")} + {Array.from(props.pinnedProtocolIds ?? []).join(",")} - - - +
), })); diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx index 4608d55a..c5fc9935 100644 --- a/clients/web/src/App.tsx +++ b/clients/web/src/App.tsx @@ -114,7 +114,7 @@ import { EMPTY_APPS_UI, EMPTY_TASKS_UI, EMPTY_LOGS_UI, - EMPTY_HISTORY_UI, + EMPTY_PROTOCOL_UI, EMPTY_NETWORK_UI, EMPTY_CONSOLE_UI, } from "./components/screens/screenUiState"; @@ -140,7 +140,7 @@ import { ConnectionInfoModal } from "./components/groups/ConnectionInfoModal/Con import { oauthDetailsFromConnectionState } from "./components/groups/ConnectionInfoContent/oauthDetailsFromConnectionState"; import { OutputValidationModal } from "./components/groups/OutputValidationModal/OutputValidationModal"; import { UrlElicitationErrorModal } from "./components/groups/UrlElicitationErrorModal/UrlElicitationErrorModal"; -import { isReplayableHistoryMethod } from "./components/groups/historyUtils.js"; +import { isReplayableProtocolMethod } from "./components/groups/protocolUtils.js"; import type { OAuthDetails } from "./components/groups/ConnectionInfoContent/ConnectionInfoContent"; import { ServerRemoveConfirmModal } from "./components/groups/ServerRemoveConfirmModal/ServerRemoveConfirmModal"; import { StepUpAuthModal } from "./components/groups/StepUpAuthModal/StepUpAuthModal"; @@ -267,21 +267,21 @@ function messagesToLogEntries(messages: MessageEntry[]): LogEntryData[] { return out; } -// Re-issue the original request behind a History entry. The call goes through +// Re-issue the original request behind a Protocol entry. The call goes through // InspectorClient → tracked transport → message log, so the replayed -// request+response surface as a fresh History entry (history-local) — it +// request+response surface as a fresh Protocol entry (protocol-local) — it // intentionally does NOT touch the Tools/Prompts/Resources panels. Returns a // human-readable reason when the entry can't be replayed (unsupported method, // or a tool that's no longer present), or null on a dispatched replay. -async function replayHistoryRequest( +async function replayProtocolRequest( client: InspectorClient, method: string, params: Record | undefined, tools: Tool[], ): Promise { - // Gate on the shared replayable-method set (the same one HistoryEntry uses to + // Gate on the shared replayable-method set (the same one ProtocolEntry uses to // show/hide the Replay button) so the two can't drift. - if (!isReplayableHistoryMethod(method)) { + if (!isReplayableProtocolMethod(method)) { return `Replay isn't supported for "${method}".`; } // Pagination cursor carried by the */list requests; replaying the same page @@ -755,11 +755,11 @@ function App() { const [appsUi, setAppsUi] = useState(EMPTY_APPS_UI); const [tasksUi, setTasksUi] = useState(EMPTY_TASKS_UI); const [logsUi, setLogsUi] = useState(EMPTY_LOGS_UI); - const [historyUi, setHistoryUi] = useState(EMPTY_HISTORY_UI); - // History entries the user pinned (by entry id). Session-scoped — the ids + const [protocolUi, setProtocolUi] = useState(EMPTY_PROTOCOL_UI); + // Protocol entries the user pinned (by entry id). Session-scoped — the ids // reference message-log entries, which clear on disconnect, so this resets // with the rest of the per-screen state. - const [pinnedHistoryIds, setPinnedHistoryIds] = useState>( + const [pinnedProtocolIds, setPinnedProtocolIds] = useState>( () => new Set(), ); const [networkUi, setNetworkUi] = useState(EMPTY_NETWORK_UI); @@ -1014,8 +1014,8 @@ function App() { setAppsUi(EMPTY_APPS_UI); setTasksUi(EMPTY_TASKS_UI); setLogsUi(EMPTY_LOGS_UI); - setHistoryUi(EMPTY_HISTORY_UI); - setPinnedHistoryIds(new Set()); + setProtocolUi(EMPTY_PROTOCOL_UI); + setPinnedProtocolIds(new Set()); setNetworkUi(EMPTY_NETWORK_UI); // Only the search filter resets here; the stderr entries themselves live in // StderrLogState, which deliberately survives connect/disconnect so a failed @@ -1059,7 +1059,7 @@ function App() { // Surface incoming `notifications/progress` as toasts so the user can watch a // long-running tool's progress while staying on the tool view — the v2 // replacement for v1's always-visible "Server Notifications" shelf (#1414). - // The full notification history still lives in the History tab; these toasts + // The full notification history still lives in the Protocol tab; these toasts // are the at-a-glance, in-context signal. Toasts are keyed by progress stream // (see `progressToastId`) and replaced per tick so a chatty server updates one // toast rather than stacking one per tick. @@ -1171,7 +1171,7 @@ function App() { // with `requestorTaskUpdated` so we skip it to avoid double-firing. One toast per // taskId, replaced per tick, dismissed on terminal status (which also prunes the // task's progress entry) and on client teardown. The full status history still - // lives in the History view. + // lives in the Protocol view. useEffect(() => { if (!inspectorClient) return; const liveToastIds = taskToastIdsRef.current; @@ -1560,7 +1560,7 @@ function App() { appsUi, tasksUi, logsUi, - historyUi, + protocolUi, networkUi, }), ...(remoteSessionId && { remoteSessionId }), @@ -1578,7 +1578,7 @@ function App() { appsUi, tasksUi, logsUi, - historyUi, + protocolUi, networkUi, onBeforeOAuthRedirect, ], @@ -2189,7 +2189,7 @@ function App() { setAppsUi, setTasksUi, setLogsUi, - setHistoryUi, + setProtocolUi, setNetworkUi, setActiveTab, clearToolCallState: () => setToolCallState(undefined), @@ -2987,7 +2987,7 @@ function App() { const onClearLogs = useCallback(() => { if (!messageLogState) return; // Clear only the log notifications, not the entire request/response - // history (which the History screen renders from the same source). + // history (which the Protocol screen renders from the same source). messageLogState.clearMessages( (m) => m.direction === "notification" && @@ -2999,10 +2999,10 @@ function App() { // Panel-level Clear clears the (unpinned) history and keeps pinned entries — // pinning is the way to protect an entry from Clear. This matches the button's // `disabled={unpinnedEntries.length === 0}` gating and the per-section model, - // and leaves pinnedHistoryIds valid (the pins it references still exist). - const onClearHistory = useCallback(() => { - messageLogState?.clearMessages((m) => !pinnedHistoryIds.has(m.id)); - }, [messageLogState, pinnedHistoryIds]); + // and leaves pinnedProtocolIds valid (the pins it references still exist). + const onClearProtocol = useCallback(() => { + messageLogState?.clearMessages((m) => !pinnedProtocolIds.has(m.id)); + }, [messageLogState, pinnedProtocolIds]); const onClearNetwork = useCallback(() => { fetchRequestLogState?.clearFetchRequests(); @@ -3016,50 +3016,50 @@ function App() { ); }, [fetchRequests, activeServerId]); - const onExportHistory = useCallback(() => { + const onExportProtocol = useCallback(() => { if (messages.length === 0) return; downloadJsonFile( - buildExportFilename("history", activeServerId), + buildExportFilename("protocol", activeServerId), JSON.stringify(messages, null, 2), ); }, [messages, activeServerId]); // Clear just one section: remove its entries from the log by pin membership. // Clearing the pinned section also drops the (now-stale) pinned id set. - const onClearHistorySection = useCallback( + const onClearProtocolSection = useCallback( (section: "pinned" | "history") => { const isPinned = section === "pinned"; messageLogState?.clearMessages((m) => - isPinned ? pinnedHistoryIds.has(m.id) : !pinnedHistoryIds.has(m.id), + isPinned ? pinnedProtocolIds.has(m.id) : !pinnedProtocolIds.has(m.id), ); - if (isPinned) setPinnedHistoryIds(new Set()); + if (isPinned) setPinnedProtocolIds(new Set()); }, - [messageLogState, pinnedHistoryIds], + [messageLogState, pinnedProtocolIds], ); // Export just one section's entries (by pin membership) to a JSON file. - const onExportHistorySection = useCallback( + const onExportProtocolSection = useCallback( (section: "pinned" | "history") => { const isPinned = section === "pinned"; const subset = messages.filter((m) => - isPinned ? pinnedHistoryIds.has(m.id) : !pinnedHistoryIds.has(m.id), + isPinned ? pinnedProtocolIds.has(m.id) : !pinnedProtocolIds.has(m.id), ); if (subset.length === 0) return; downloadJsonFile( buildExportFilename( - isPinned ? "history-pinned" : "history-unpinned", + isPinned ? "protocol-pinned" : "protocol-unpinned", activeServerId, ), JSON.stringify(subset, null, 2), ); }, - [messages, pinnedHistoryIds, activeServerId], + [messages, pinnedProtocolIds, activeServerId], ); - // Pin/unpin a history entry by id. HistoryListPanel sorts pinned entries to + // Pin/unpin a Protocol entry by id. ProtocolListPanel sorts pinned entries to // the top; the set is session-scoped (see resetSessionScopedUiState). - const onTogglePinHistory = useCallback((id: string) => { - setPinnedHistoryIds((prev) => { + const onTogglePinProtocol = useCallback((id: string) => { + setPinnedProtocolIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); @@ -3070,12 +3070,12 @@ function App() { }); }, []); - // Replay a history entry: re-issue its original request so the fresh - // request+response appear as a new History entry (history-local). A reason + // Replay a Protocol entry: re-issue its original request so the fresh + // request+response appear as a new Protocol entry (protocol-local). A reason // string (unsupported method / missing tool) surfaces as a toast; a genuine // call error already shows up as the replayed entry's Error status, so only a // pre-flight failure (nothing logged) needs the fallback toast. - const onReplayHistory = useCallback( + const onReplayProtocol = useCallback( (id: string) => { if (!inspectorClient) return; const entry = messages.find((m) => m.id === id); @@ -3085,7 +3085,7 @@ function App() { "params" in entry.message ? (entry.message.params as Record | undefined) : undefined; - void replayHistoryRequest(inspectorClient, method, params, tools) + void replayProtocolRequest(inspectorClient, method, params, tools) .then((reason) => { if (reason) { notifications.show({ @@ -3687,7 +3687,7 @@ function App() { logs={logs} tasks={tasks} progressByTaskId={progressByTaskId} - history={messages} + protocol={messages} network={fetchRequests} stderrLogs={stderrLogs} toolCallState={toolCallState} @@ -3699,7 +3699,7 @@ function App() { appsUi={appsUi} tasksUi={tasksUi} logsUi={logsUi} - historyUi={historyUi} + protocolUi={protocolUi} networkUi={networkUi} consoleUi={consoleUi} activeTab={activeTab} @@ -3792,14 +3792,14 @@ function App() { onLogsUiChange={setLogsUi} onClearLogs={onClearLogs} onExportLogs={onExportLogs} - onHistoryUiChange={setHistoryUi} - onClearHistory={onClearHistory} - onExportHistory={onExportHistory} - onClearHistorySection={onClearHistorySection} - onExportHistorySection={onExportHistorySection} - onReplayHistory={onReplayHistory} - onTogglePinHistory={onTogglePinHistory} - pinnedHistoryIds={pinnedHistoryIds} + onProtocolUiChange={setProtocolUi} + onClearProtocol={onClearProtocol} + onExportProtocol={onExportProtocol} + onClearProtocolSection={onClearProtocolSection} + onExportProtocolSection={onExportProtocolSection} + onReplayProtocol={onReplayProtocol} + onTogglePinProtocol={onTogglePinProtocol} + pinnedProtocolIds={pinnedProtocolIds} onNetworkUiChange={setNetworkUi} onClearNetwork={onClearNetwork} onExportNetwork={onExportNetwork} diff --git a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx index 55c78e53..a718f797 100644 --- a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx +++ b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.test.tsx @@ -31,4 +31,27 @@ describe("EmbeddableScrollArea", () => { ); expect(ref.current).toBeInstanceOf(HTMLElement); }); + + it.each([false, true])( + "constrains the content width when asked (embedded=%s)", + (embedded) => { + const ref = createRef(); + renderWithMantine( + +
constrained content
+
, + ); + expect(screen.getByText("constrained content")).toBeInTheDocument(); + // The Mantine ScrollArea `content` slot has its default + // `min-width: max-content` relaxed to 0 so a long non-wrapping row + // (e.g. a network URL) can't stretch the list past its viewport (#1623). + const content = document.querySelector(".mantine-ScrollArea-content"); + expect(content).not.toBeNull(); + expect((content as HTMLElement).style.minWidth).toBe("0"); + }, + ); }); diff --git a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx index 34dac26b..814bc7f7 100644 --- a/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx +++ b/clients/web/src/components/elements/EmbeddableScrollArea/EmbeddableScrollArea.tsx @@ -17,10 +17,25 @@ export interface EmbeddableScrollAreaProps { embedded: boolean; viewportRef: Ref; children: ReactNode; + /** + * Constrain the scrolled content to the viewport width instead of letting it + * grow to its own `max-content`. Mantine's ScrollArea `content` slot defaults + * to `min-width: max-content`, so a row with non-wrapping content (e.g. a long + * network URL) stretches every card past the column and it bleeds out (#1623). + * When true, the content can shrink to the viewport and each row must manage + * its own overflow (the Network URL scrolls inside its own inner ScrollArea). + * Left off for panels whose rows already wrap/truncate (Logs, Protocol), where + * the default lets a long line scroll the list horizontally instead. + */ + constrainContentWidth?: boolean; } +// Relax the `content` slot's default `min-width: max-content` so the list can't +// grow wider than its viewport; see `constrainContentWidth`. +const CONSTRAIN_CONTENT_STYLES = { content: { minWidth: 0 } } as const; + /** - * The scroll region shared by the Logs / History / Network stream panels, which + * The scroll region shared by the Logs / Protocol / Network stream panels, which * render both full-size (their own tab) and embedded (the monitoring column). * Centralizes the one layout difference between those two hosts. */ @@ -28,7 +43,9 @@ export function EmbeddableScrollArea({ embedded, viewportRef, children, + constrainContentWidth = false, }: EmbeddableScrollAreaProps) { + const styles = constrainContentWidth ? CONSTRAIN_CONTENT_STYLES : undefined; if (embedded) { return ( @@ -37,6 +54,7 @@ export function EmbeddableScrollArea({ mah="100%" type="scroll" offsetScrollbars + styles={styles} > {children} @@ -49,6 +67,7 @@ export function EmbeddableScrollArea({ mah={FULLSIZE_MAH} type="scroll" offsetScrollbars + styles={styles} > {children} diff --git a/clients/web/src/components/elements/ExpandToggle/ExpandToggle.tsx b/clients/web/src/components/elements/ExpandToggle/ExpandToggle.tsx index 458caf6e..b71cbabe 100644 --- a/clients/web/src/components/elements/ExpandToggle/ExpandToggle.tsx +++ b/clients/web/src/components/elements/ExpandToggle/ExpandToggle.tsx @@ -8,7 +8,7 @@ export interface ExpandToggleProps { } /** - * Icon toggle for a per-entry expand/collapse control (History, Network, and + * Icon toggle for a per-entry expand/collapse control (Protocol, Network, and * Task cards). Uses the same expand/collapse-vertical icons as the list-level * ListToggle: collapsed shows the expand icon, expanded shows the collapse * icon. The aria-label stays "Expand"/"Collapse" so it reads the same as the diff --git a/clients/web/src/components/elements/FilterToggleButton/FilterToggleButton.tsx b/clients/web/src/components/elements/FilterToggleButton/FilterToggleButton.tsx index 324613df..42161b65 100644 --- a/clients/web/src/components/elements/FilterToggleButton/FilterToggleButton.tsx +++ b/clients/web/src/components/elements/FilterToggleButton/FilterToggleButton.tsx @@ -17,7 +17,7 @@ const ToggleLabel = Text.withProps({ }); /** - * A single full-width filter toggle used by the Logging, History, and Network + * A single full-width filter toggle used by the Logging, Protocol, and Network * controls. The `filterToggle` theme variant + `.filter-toggle` rules own the * styling: hover shows a thin border, the active (`aria-pressed`) state shows a * filled background. Keeping hover as a border (not a fill) means toggling a diff --git a/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.stories.tsx b/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.stories.tsx index 51a34ff8..a4aa55e0 100644 --- a/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.stories.tsx +++ b/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.stories.tsx @@ -22,6 +22,6 @@ export const Incoming: Story = { args: { direction: "incoming" }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); - expect(canvas.getByText("client ← server")).toBeInTheDocument(); + expect(canvas.getByText("server → client")).toBeInTheDocument(); }, }; diff --git a/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.test.tsx b/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.test.tsx index 6c297daa..d7257206 100644 --- a/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.test.tsx +++ b/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.test.tsx @@ -8,8 +8,8 @@ describe("MessageDirectionBadge", () => { expect(screen.getByText("client → server")).toBeInTheDocument(); }); - it("renders client ← server for incoming", () => { + it("renders server → client for incoming", () => { renderWithMantine(); - expect(screen.getByText("client ← server")).toBeInTheDocument(); + expect(screen.getByText("server → client")).toBeInTheDocument(); }); }); diff --git a/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.tsx b/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.tsx index 5478296e..785456cb 100644 --- a/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.tsx +++ b/clients/web/src/components/elements/MessageDirectionBadge/MessageDirectionBadge.tsx @@ -4,21 +4,21 @@ export interface MessageDirectionBadgeProps { /** * Direction of travel for the entry: "outgoing" = the inspector sent it to * the server (client → server); "incoming" = the server sent it to the - * inspector (client ← server). + * inspector (server → client). */ direction: "outgoing" | "incoming"; } const LABEL: Record = { outgoing: "client → server", - incoming: "client ← server", + incoming: "server → client", }; /** - * Dual-state badge showing which way a History/Network entry traveled. Outgoing - * (client → server) is green; incoming (client ← server) is purple — not yellow, + * Dual-state badge showing which way a Protocol/Network entry traveled. Outgoing + * (client → server) is green; incoming (server → client) is purple — not yellow, * which (paired with green) reads as caution/ok status rather than direction. - * Shared by `HistoryEntry` and `NetworkEntry`. + * Shared by `ProtocolEntry` and `NetworkEntry`. */ export function MessageDirectionBadge({ direction, diff --git a/clients/web/src/components/elements/PinColumnButton/PinColumnButton.tsx b/clients/web/src/components/elements/PinColumnButton/PinColumnButton.tsx index cd252845..8689eaae 100644 --- a/clients/web/src/components/elements/PinColumnButton/PinColumnButton.tsx +++ b/clients/web/src/components/elements/PinColumnButton/PinColumnButton.tsx @@ -7,7 +7,7 @@ export interface PinColumnButtonProps { } /** - * Toolbar button that pins the owning monitor screen (Logs / History / Network) + * Toolbar button that pins the owning monitor screen (Logs / Protocol / Network) * into the resizable column on the right of the InspectorView (#1616). Distinct * from `PinToggle` (which pins individual history entries) — this one opens a * side column, so it uses a right-sidebar glyph and an "as column" label. Styled diff --git a/clients/web/src/components/elements/ReplayButton/ReplayButton.tsx b/clients/web/src/components/elements/ReplayButton/ReplayButton.tsx index 6cac503e..3c8c13e6 100644 --- a/clients/web/src/components/elements/ReplayButton/ReplayButton.tsx +++ b/clients/web/src/components/elements/ReplayButton/ReplayButton.tsx @@ -7,7 +7,7 @@ export interface ReplayButtonProps { } /** - * Icon form of the "Replay" action, used in the compact (column) HistoryEntry + * Icon form of the "Replay" action, used in the compact (column) ProtocolEntry * layout where the text button is replaced by a replay icon sitting next to the * pin toggle (#1616). Matches PinToggle's subtle gray icon-button styling. */ diff --git a/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.test.tsx b/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.test.tsx index 33b96770..eb19384e 100644 --- a/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.test.tsx +++ b/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.test.tsx @@ -17,7 +17,7 @@ describe("MessageDirectionFilter", () => { screen.getByRole("button", { name: "client → server" }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: "client ← server" }), + screen.getByRole("button", { name: "server → client" }), ).toBeInTheDocument(); }); @@ -30,7 +30,7 @@ describe("MessageDirectionFilter", () => { onToggleDirection={onToggleDirection} />, ); - await user.click(screen.getByRole("button", { name: "client ← server" })); + await user.click(screen.getByRole("button", { name: "server → client" })); expect(onToggleDirection).toHaveBeenCalledWith("server", false); }); diff --git a/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.tsx b/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.tsx index b85f554d..033f3871 100644 --- a/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.tsx +++ b/clients/web/src/components/groups/MessageDirectionFilter/MessageDirectionFilter.tsx @@ -9,14 +9,14 @@ const SubtleButton = Button.withProps({ // The two message directions, in display order. Label + color mirror the // MessageDirectionBadge: outgoing (client → server) is green, incoming -// (client ← server) is violet. +// (server → client) is violet. const MESSAGE_DIRECTIONS: { origin: MessageOrigin; label: string; color: string; }[] = [ { origin: "client", label: "client → server", color: "green" }, - { origin: "server", label: "client ← server", color: "violet" }, + { origin: "server", label: "server → client", color: "violet" }, ]; export interface MessageDirectionFilterProps { @@ -27,8 +27,8 @@ export interface MessageDirectionFilterProps { /** * "Filter by Message Direction" section — a Select/Deselect All control plus a - * FilterToggleButton per direction (client → server / client ← server). Used by - * the History controls. (Kept as its own component so the section is testable in + * FilterToggleButton per direction (client → server / server → client). Used by + * the Protocol controls. (Kept as its own component so the section is testable in * isolation and reusable if another screen ever needs a direction filter.) */ export function MessageDirectionFilter({ diff --git a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.stories.tsx b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.stories.tsx index a176508e..6a869af4 100644 --- a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.stories.tsx +++ b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.stories.tsx @@ -6,7 +6,7 @@ const meta: Meta = { title: "Groups/MonitoringControls", component: MonitoringControls, args: { - tabs: ["Logs", "History", "Network"], + tabs: ["Logs", "Protocol", "Network"], value: "Logs", onChange: fn(), searchValue: "", @@ -29,7 +29,7 @@ export const AllTabs: Story = { }; export const StdioServer: Story = { - args: { tabs: ["Logs", "History"], value: "History" }, + args: { tabs: ["Logs", "Protocol"], value: "Protocol" }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.queryByRole("radio", { name: "Network" })).toBeNull(); diff --git a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.test.tsx b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.test.tsx index 8cf86503..3914ed21 100644 --- a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.test.tsx +++ b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.test.tsx @@ -6,7 +6,7 @@ import { type MonitoringControlsProps, } from "./MonitoringControls"; -const TABS = ["Logs", "History", "Network"]; +const TABS = ["Logs", "Protocol", "Network"]; function renderControls(overrides: Partial = {}) { const props: MonitoringControlsProps = { @@ -31,7 +31,7 @@ describe("MonitoringControls", () => { }); it("renders only the tabs it is given", () => { - renderControls({ tabs: ["Logs", "History"] }); + renderControls({ tabs: ["Logs", "Protocol"] }); expect(screen.getAllByRole("radio")).toHaveLength(2); expect(screen.queryByRole("radio", { name: "Network" })).toBeNull(); }); @@ -53,8 +53,8 @@ describe("MonitoringControls", () => { }); it("marks the active tab as selected", () => { - renderControls({ value: "History" }); - expect(screen.getByRole("radio", { name: "History" })).toBeChecked(); + renderControls({ value: "Protocol" }); + expect(screen.getByRole("radio", { name: "Protocol" })).toBeChecked(); }); it("calls onChange when a different tab is chosen", async () => { diff --git a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx index 58c79cc1..28c8fbcf 100644 --- a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx +++ b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx @@ -3,7 +3,7 @@ import { TbLayoutSidebarRightCollapse } from "react-icons/tb"; import { ClearButton } from "../../elements/ClearButton/ClearButton"; export interface MonitoringControlsProps { - /** The monitor screens currently available to pin (e.g. Logs/History/Network). */ + /** The monitor screens currently available to pin (e.g. Logs/Protocol/Network). */ tabs: string[]; /** The active monitor tab shown in the column below. */ value: string; diff --git a/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.stories.tsx b/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.stories.tsx index 8d934c82..2249dfe1 100644 --- a/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.stories.tsx +++ b/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.stories.tsx @@ -9,7 +9,7 @@ const demoScreens = { Log stream ), - History: ( + Protocol: ( Request history @@ -32,7 +32,7 @@ const meta: Meta = { ), ], args: { - tabs: ["Logs", "History", "Network"], + tabs: ["Logs", "Protocol", "Network"], value: "Logs", onChange: fn(), searchValue: "", @@ -52,8 +52,8 @@ export const Logs: Story = { }, }; -export const History: Story = { - args: { value: "History" }, +export const Protocol: Story = { + args: { value: "Protocol" }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByText("Request history")).toBeInTheDocument(); diff --git a/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.test.tsx b/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.test.tsx index 33f89ace..77ce2c4b 100644 --- a/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.test.tsx +++ b/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.test.tsx @@ -6,12 +6,12 @@ import { type MonitoringScreenProps, } from "./MonitoringScreen"; -const TABS = ["Logs", "History", "Network"]; +const TABS = ["Logs", "Protocol", "Network"]; function screens() { return { Logs:
logs-body
, - History:
history-body
, + Protocol:
history-body
, Network:
network-body
, }; } @@ -33,7 +33,7 @@ function renderScreen(overrides: Partial = {}) { describe("MonitoringScreen", () => { it("renders the screen for the active tab", () => { - renderScreen({ value: "History" }); + renderScreen({ value: "Protocol" }); expect(screen.getByText("history-body")).toBeInTheDocument(); expect(screen.queryByText("logs-body")).toBeNull(); expect(screen.queryByText("network-body")).toBeNull(); diff --git a/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.tsx b/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.tsx index 2d9f392d..577502e0 100644 --- a/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.tsx +++ b/clients/web/src/components/groups/MonitoringScreen/MonitoringScreen.tsx @@ -3,7 +3,7 @@ import { Divider, Stack } from "@mantine/core"; import { MonitoringControls } from "../MonitoringControls/MonitoringControls"; export interface MonitoringScreenProps { - /** Available monitor tabs (Logs/History/Network, filtered by capability). */ + /** Available monitor tabs (Logs/Protocol/Network, filtered by capability). */ tabs: string[]; /** Active monitor tab; keys into `screens` to pick what renders below. */ value: string; diff --git a/clients/web/src/components/groups/NetworkEntry/NetworkEntry.tsx b/clients/web/src/components/groups/NetworkEntry/NetworkEntry.tsx index 08bb937b..40d8950c 100644 --- a/clients/web/src/components/groups/NetworkEntry/NetworkEntry.tsx +++ b/clients/web/src/components/groups/NetworkEntry/NetworkEntry.tsx @@ -58,7 +58,7 @@ const UrlScroll = Text.withProps({ variant: "nowrap", }); -// Left / right clusters for a compact header line (mirrors HistoryEntry). +// Left / right clusters for a compact header line (mirrors ProtocolEntry). const HeaderCluster = Group.withProps({ gap: "sm", wrap: "nowrap", @@ -240,7 +240,7 @@ export function NetworkEntry({ // The list-level Expand/Collapse toggle is authoritative: each time the // parent changes `isListExpanded`, every entry snaps to that state and // any per-entry override is intentionally discarded. Mirrors - // HistoryEntry; do not change without aligning both. This re-runs on + // ProtocolEntry; do not change without aligning both. This re-runs on // re-render when `isListExpanded` keeps its reference, but the setter // is a no-op when the next value equals the current one. useEffect(() => { diff --git a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx index 56f88610..a4248d1e 100644 --- a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx +++ b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.stories.tsx @@ -57,3 +57,39 @@ export const WithEntries: Story = { export const Empty: Story = { args: { entries: [] }, }; + +const longUrlSample: FetchRequestEntry[] = [ + { + id: "n-long-1", + timestamp: new Date("2026-03-17T10:00:00Z"), + method: "GET", + url: "https://example-server.modelcontextprotocol.io/.well-known/oauth-protected-resource/mcp", + requestHeaders: {}, + responseStatus: 404, + duration: 84, + category: "auth", + }, + { + id: "n-long-2", + timestamp: new Date("2026-03-17T10:00:05Z"), + method: "GET", + url: "https://example-server.modelcontextprotocol.io/.well-known/oauth-authorization-server", + requestHeaders: {}, + responseStatus: 200, + duration: 79, + category: "auth", + }, +]; + +// Reproduces the pinned-column overflow: a long URL must scroll horizontally +// inside its own area rather than widening the card past the column (#1623). +export const EmbeddedLongUrls: Story = { + args: { entries: longUrlSample, embedded: true }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; diff --git a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx index 509f5a98..9aa7f6e8 100644 --- a/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx +++ b/clients/web/src/components/groups/NetworkStreamPanel/NetworkStreamPanel.tsx @@ -140,7 +140,11 @@ export function NetworkStreamPanel({ {!hasResults ? ( No network requests ) : ( - + {filteredEntries.map((entry) => ( = { - title: "Groups/HistoryControls", - component: HistoryControls, +const meta: Meta = { + title: "Groups/ProtocolControls", + component: ProtocolControls, args: { searchText: "", availableMethods: SAMPLE_METHODS, @@ -29,7 +29,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; diff --git a/clients/web/src/components/groups/HistoryControls/HistoryControls.test.tsx b/clients/web/src/components/groups/ProtocolControls/ProtocolControls.test.tsx similarity index 81% rename from clients/web/src/components/groups/HistoryControls/HistoryControls.test.tsx rename to clients/web/src/components/groups/ProtocolControls/ProtocolControls.test.tsx index d727b32c..c182bd46 100644 --- a/clients/web/src/components/groups/HistoryControls/HistoryControls.test.tsx +++ b/clients/web/src/components/groups/ProtocolControls/ProtocolControls.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { MessageMethod } from "@inspector/core/mcp/types.js"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; -import { HistoryControls } from "./HistoryControls"; +import { ProtocolControls } from "./ProtocolControls"; const baseProps = { searchText: "", @@ -14,10 +14,10 @@ const baseProps = { onToggleAllDirections: vi.fn(), }; -describe("HistoryControls", () => { +describe("ProtocolControls", () => { it("renders the title and search input", () => { - renderWithMantine(); - expect(screen.getByText("History")).toBeInTheDocument(); + renderWithMantine(); + expect(screen.getByText("Protocol")).toBeInTheDocument(); expect(screen.getByPlaceholderText("Search...")).toBeInTheDocument(); }); @@ -25,7 +25,7 @@ describe("HistoryControls", () => { const user = userEvent.setup(); const onSearchChange = vi.fn(); renderWithMantine( - , + , ); await user.type(screen.getByPlaceholderText("Search..."), "a"); expect(onSearchChange).toHaveBeenCalledWith("a"); @@ -35,7 +35,7 @@ describe("HistoryControls", () => { const user = userEvent.setup(); const onSearchChange = vi.fn(); renderWithMantine( - { }); it("keeps the search Clear button out of the keyboard tab order", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByRole("button", { name: "Clear" })).toHaveAttribute( "tabindex", "-1", @@ -54,13 +54,13 @@ describe("HistoryControls", () => { }); it("renders the method filter placeholder", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByPlaceholderText("All methods")).toBeInTheDocument(); }); it("displays the active method filter value", () => { renderWithMantine( - , + , ); const inputs = screen.getAllByDisplayValue("tools/list"); expect(inputs.length).toBeGreaterThan(0); @@ -70,7 +70,7 @@ describe("HistoryControls", () => { const user = userEvent.setup(); const onMethodFilterChange = vi.fn(); const { container } = renderWithMantine( - { }); it("renders the Filter by Message Direction section with both directions", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByText("Filter by Message Direction")).toBeInTheDocument(); expect( screen.getByRole("button", { name: "client → server" }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: "client ← server" }), + screen.getByRole("button", { name: "server → client" }), ).toBeInTheDocument(); }); @@ -99,10 +99,10 @@ describe("HistoryControls", () => { const user = userEvent.setup(); const onToggleDirection = vi.fn(); renderWithMantine( - , + , ); // Currently visible → clicking turns it off. - await user.click(screen.getByRole("button", { name: "client ← server" })); + await user.click(screen.getByRole("button", { name: "server → client" })); expect(onToggleDirection).toHaveBeenCalledWith("server", false); }); @@ -110,7 +110,7 @@ describe("HistoryControls", () => { const user = userEvent.setup(); const onToggleAllDirections = vi.fn(); renderWithMantine( - , diff --git a/clients/web/src/components/groups/HistoryControls/HistoryControls.tsx b/clients/web/src/components/groups/ProtocolControls/ProtocolControls.tsx similarity index 92% rename from clients/web/src/components/groups/HistoryControls/HistoryControls.tsx rename to clients/web/src/components/groups/ProtocolControls/ProtocolControls.tsx index 7e262976..5803c0f7 100644 --- a/clients/web/src/components/groups/HistoryControls/HistoryControls.tsx +++ b/clients/web/src/components/groups/ProtocolControls/ProtocolControls.tsx @@ -6,7 +6,7 @@ import type { } from "@inspector/core/mcp/types.js"; import { MessageDirectionFilter } from "../MessageDirectionFilter/MessageDirectionFilter"; -export interface HistoryControlsProps { +export interface ProtocolControlsProps { searchText: string; methodFilter?: MessageMethod; availableMethods: MessageMethod[]; @@ -17,7 +17,7 @@ export interface HistoryControlsProps { onToggleAllDirections: () => void; } -export function HistoryControls({ +export function ProtocolControls({ searchText, methodFilter, availableMethods, @@ -26,10 +26,10 @@ export function HistoryControls({ onMethodFilterChange, onToggleDirection, onToggleAllDirections, -}: HistoryControlsProps) { +}: ProtocolControlsProps) { return ( - History + Protocol = { - title: "Groups/HistoryEntry", - component: HistoryEntry, +const meta: Meta = { + title: "Groups/ProtocolEntry", + component: ProtocolEntry, args: { onReplay: fn(), onTogglePin: fn(), @@ -13,7 +13,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; const toolCallEntry: MessageEntry = { id: "req-1", diff --git a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.test.tsx b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx similarity index 85% rename from clients/web/src/components/groups/HistoryEntry/HistoryEntry.test.tsx rename to clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx index 2abbb6fa..16560220 100644 --- a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.test.tsx +++ b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { MessageEntry } from "@inspector/core/mcp/types.js"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; -import { HistoryEntry } from "./HistoryEntry"; +import { ProtocolEntry } from "./ProtocolEntry"; const successEntry: MessageEntry = { id: "req-1", @@ -114,9 +114,9 @@ const baseProps = { onTogglePin: vi.fn(), }; -describe("HistoryEntry", () => { +describe("ProtocolEntry", () => { it("renders the method, target name, status OK, and duration", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByText("tools/call")).toBeInTheDocument(); expect(screen.getByText("get_weather")).toBeInTheDocument(); expect(screen.getByText("OK")).toBeInTheDocument(); @@ -125,25 +125,25 @@ describe("HistoryEntry", () => { it("renders the URI target for resources/read", () => { renderWithMantine( - , + , ); expect(screen.getByText("file:///config.json")).toBeInTheDocument(); expect(screen.getByText("resources/read")).toBeInTheDocument(); }); it("renders Error status when response has error", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByText("Error")).toBeInTheDocument(); }); it("renders Pending status when no response present", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByText("Pending")).toBeInTheDocument(); }); it("renders no request-style status badge for a notification", () => { renderWithMantine( - , + , ); // The method badge still labels it; there is no Pending/OK/Error badge, // since a fire-and-forget notification has no request lifecycle. @@ -154,25 +154,25 @@ describe("HistoryEntry", () => { }); it("shows client → server for a client-originated entry", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByText("client → server")).toBeInTheDocument(); }); - it("shows client ← server for a server-originated entry", () => { + it("shows server → client for a server-originated entry", () => { renderWithMantine( - , + , ); - expect(screen.getByText("client ← server")).toBeInTheDocument(); + expect(screen.getByText("server → client")).toBeInTheDocument(); }); it("renders Pin label when not pinned", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByRole("button", { name: "Pin" })).toBeInTheDocument(); }); it("renders Unpin label when pinned", () => { renderWithMantine( - , + , ); expect(screen.getByRole("button", { name: "Unpin" })).toBeInTheDocument(); }); @@ -181,7 +181,7 @@ describe("HistoryEntry", () => { const user = userEvent.setup(); const onReplay = vi.fn(); renderWithMantine( - , + , ); await user.click(screen.getByRole("button", { name: "Replay" })); expect(onReplay).toHaveBeenCalledTimes(1); @@ -190,7 +190,7 @@ describe("HistoryEntry", () => { it("hides the Replay button for a method that can't be replayed", () => { // A notification isn't a replayable client→server request. renderWithMantine( - , + , ); expect( screen.queryByRole("button", { name: "Replay" }), @@ -200,7 +200,7 @@ describe("HistoryEntry", () => { }); it("orders the actions Replay, then Pin, then the expand toggle on the right", () => { - renderWithMantine(); + renderWithMantine(); const names = screen .getAllByRole("button") .map((b) => b.getAttribute("aria-label") ?? b.textContent); @@ -210,7 +210,7 @@ describe("HistoryEntry", () => { it("renders the compact two-line layout with Replay as an icon when embedded", () => { renderWithMantine( - , + , ); // Line 1 essentials plus the method are still shown. The timestamp is the // compact time-only form (not the full ISO) to fit the narrow line. @@ -229,7 +229,7 @@ describe("HistoryEntry", () => { it("keeps action order Replay, Pin, Expand in the compact layout", () => { renderWithMantine( - , + , ); const names = screen .getAllByRole("button") @@ -248,7 +248,7 @@ describe("HistoryEntry", () => { message: { jsonrpc: "2.0", id: 1, result: {} }, }; renderWithMantine( - , + , ); expect( screen.queryByRole("button", { name: "Replay" }), @@ -260,7 +260,7 @@ describe("HistoryEntry", () => { const user = userEvent.setup(); const onTogglePin = vi.fn(); renderWithMantine( - { it("toggles the local expand/collapse state when clicking Expand/Collapse", async () => { const user = userEvent.setup(); - renderWithMantine(); + renderWithMantine(); expect(screen.getByRole("button", { name: "Expand" })).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Expand" })); expect( @@ -283,7 +283,7 @@ describe("HistoryEntry", () => { it("toggles the local expand/collapse state when clicking Expand/Collapse in the compact layout", async () => { const user = userEvent.setup(); renderWithMantine( - , + , ); expect(screen.getByRole("button", { name: "Expand" })).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Expand" })); @@ -294,7 +294,7 @@ describe("HistoryEntry", () => { it("starts expanded when isListExpanded is true and shows Parameters and Response", () => { renderWithMantine( - { it("syncs the local expanded state when isListExpanded prop changes", () => { const { rerender } = renderWithMantine( - , + , ); expect(screen.getByRole("button", { name: "Expand" })).toBeInTheDocument(); rerender( - { it("does not render the Parameters section when message has no params", () => { renderWithMantine( - { it("does not render the Response section when no response is present", () => { renderWithMantine( - { }); it("does not render duration when duration is undefined", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.queryByText(/ms$/)).not.toBeInTheDocument(); }); }); diff --git a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx similarity index 97% rename from clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx rename to clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx index a76ad4f9..c31ef65b 100644 --- a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx +++ b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx @@ -15,9 +15,9 @@ import { MessageDirectionBadge } from "../../elements/MessageDirectionBadge/Mess import { ExpandToggle } from "../../elements/ExpandToggle/ExpandToggle"; import { PinToggle } from "../../elements/PinToggle/PinToggle"; import { ReplayButton } from "../../elements/ReplayButton/ReplayButton"; -import { extractMethod, isReplayableHistoryMethod } from "../historyUtils.js"; +import { extractMethod, isReplayableProtocolMethod } from "../protocolUtils.js"; -export interface HistoryEntryProps { +export interface ProtocolEntryProps { entry: MessageEntry; isPinned: boolean; isListExpanded: boolean; @@ -128,19 +128,19 @@ function serializeMessage(value: unknown): string { return JSON.stringify(value); } -export function HistoryEntry({ +export function ProtocolEntry({ entry, isPinned, isListExpanded, onReplay, onTogglePin, embedded = false, -}: HistoryEntryProps) { +}: ProtocolEntryProps) { const [isExpanded, setIsExpanded] = useState(isListExpanded); const method = extractMethod(entry); const target = extractTarget(entry); const status = extractStatus(entry); - const canReplay = isReplayableHistoryMethod(method); + const canReplay = isReplayableProtocolMethod(method); useEffect(() => { setIsExpanded(isListExpanded); diff --git a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx b/clients/web/src/components/groups/ProtocolListPanel/ProtocolListPanel.stories.tsx similarity index 92% rename from clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx rename to clients/web/src/components/groups/ProtocolListPanel/ProtocolListPanel.stories.tsx index d27dbbc0..33f8a4a6 100644 --- a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx +++ b/clients/web/src/components/groups/ProtocolListPanel/ProtocolListPanel.stories.tsx @@ -1,12 +1,12 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { fn } from "storybook/test"; -import { HistoryListPanel } from "./HistoryListPanel.js"; +import { ProtocolListPanel } from "./ProtocolListPanel.js"; import { expectScrollbarGutterIdleHidden } from "../../../test/scrollAreaStoryAssertions"; -const meta: Meta = { - title: "Groups/HistoryListPanel", - component: HistoryListPanel, +const meta: Meta = { + title: "Groups/ProtocolListPanel", + component: ProtocolListPanel, args: { searchText: "", pinnedIds: new Set(), @@ -24,7 +24,7 @@ const meta: Meta = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; const sampleEntries: MessageEntry[] = [ { diff --git a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.test.tsx b/clients/web/src/components/groups/ProtocolListPanel/ProtocolListPanel.test.tsx similarity index 90% rename from clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.test.tsx rename to clients/web/src/components/groups/ProtocolListPanel/ProtocolListPanel.test.tsx index 4469d6e1..115fa1d3 100644 --- a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.test.tsx +++ b/clients/web/src/components/groups/ProtocolListPanel/ProtocolListPanel.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { MessageEntry } from "@inspector/core/mcp/types.js"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; -import { HistoryListPanel } from "./HistoryListPanel"; +import { ProtocolListPanel } from "./ProtocolListPanel"; const sampleEntries: MessageEntry[] = [ { @@ -75,21 +75,21 @@ const baseProps = { onToggleCompact: vi.fn(), }; -describe("HistoryListPanel", () => { +describe("ProtocolListPanel", () => { it("renders the title and Export button", () => { - renderWithMantine(); - expect(screen.getByText("Requests")).toBeInTheDocument(); + renderWithMantine(); + expect(screen.getByText("Messages")).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Export" })).toBeInTheDocument(); }); it("renders the empty state when there are no entries", () => { - renderWithMantine(); + renderWithMantine(); expect(screen.getByText("No request history")).toBeInTheDocument(); }); it("renders the empty state when entries exist but none match the filter", () => { renderWithMantine( - { it("hides the History header when there are no pinned entries", () => { renderWithMantine( - , + , ); // With no pinned section to distinguish it from, the header is dropped... expect(screen.queryByText("History (3)")).toBeNull(); @@ -110,13 +110,13 @@ describe("HistoryListPanel", () => { it("renders the Pinned title with count when entries are pinned", () => { renderWithMantine( - , ); - expect(screen.getByText("Pinned Requests (1)")).toBeInTheDocument(); + expect(screen.getByText("Pinned Messages (1)")).toBeInTheDocument(); expect(screen.getByText("History (2)")).toBeInTheDocument(); }); @@ -124,7 +124,7 @@ describe("HistoryListPanel", () => { const user = userEvent.setup(); // Both sections present so the headers are collapsible toggles. renderWithMantine( - { // Only the unpinned section → no header at all, no accordion toggle, // entries always visible. renderWithMantine( - , + , ); expect(screen.queryByText("History (3)")).toBeNull(); expect( @@ -155,7 +155,7 @@ describe("HistoryListPanel", () => { it("shows the surviving section's entries after the other is removed, even if it was collapsed", async () => { const user = userEvent.setup(); const { rerender } = renderWithMantine( - { ); // Remove the pinned section → History is now the only section. Its entries // must show despite the stale collapsed state, and the header is plain. - rerender(); + rerender(); expect( screen.queryByRole("button", { name: "History (3)" }), ).not.toBeInTheDocument(); @@ -179,14 +179,14 @@ describe("HistoryListPanel", () => { it("collapses the Pinned and History sections independently", async () => { const user = userEvent.setup(); renderWithMantine( - , ); const pinned = screen.getByRole("button", { - name: "Pinned Requests (1)", + name: "Pinned Messages (1)", }); const history = screen.getByRole("button", { name: "History (2)" }); await user.click(pinned); @@ -198,7 +198,7 @@ describe("HistoryListPanel", () => { it("shows per-section Clear/Export only when both sections are present", () => { // Only an unpinned section → just the panel-level Clear/Export. const { unmount } = renderWithMantine( - , + , ); expect(screen.getAllByRole("button", { name: "Clear" })).toHaveLength(1); expect(screen.getAllByRole("button", { name: "Export" })).toHaveLength(1); @@ -206,7 +206,7 @@ describe("HistoryListPanel", () => { // Both sections → panel-level plus one Clear/Export per section. renderWithMantine( - { const onClearSection = vi.fn(); const onExportSection = vi.fn(); renderWithMantine( - { { ...sampleEntries[1], id: "from-server", origin: "server" }, ]; renderWithMantine( - { it("filters entries by searchText (case-insensitive)", () => { renderWithMantine( - { it("filters entries by methodFilter", () => { renderWithMantine( - { const user = userEvent.setup(); const onExport = vi.fn(); renderWithMantine( - { const user = userEvent.setup(); const onClearAll = vi.fn(); renderWithMantine( - { const user = userEvent.setup(); const onReplay = vi.fn(); renderWithMantine( - { const user = userEvent.setup(); const onTogglePin = vi.fn(); renderWithMantine( - { it("renders only pinned section when all entries are pinned", () => { renderWithMantine( - , ); - expect(screen.getByText("Pinned Requests (1)")).toBeInTheDocument(); + expect(screen.getByText("Pinned Messages (1)")).toBeInTheDocument(); expect(screen.queryByText(/^History \(/)).not.toBeInTheDocument(); }); @@ -355,7 +355,7 @@ describe("HistoryListPanel", () => { const onReplay = vi.fn(); const onTogglePin = vi.fn(); renderWithMantine( - { it("renders entries newest-first by default", () => { renderWithMantine( - , + , ); const methods = screen.getAllByText( /tools\/call|resources\/read|tools\/list/, @@ -382,7 +382,7 @@ describe("HistoryListPanel", () => { it("reorders entries when sortDirection is oldest-first", () => { renderWithMantine( - { const user = userEvent.setup(); const onSortChange = vi.fn(); renderWithMantine( - { it("renders entries collapsed when compact is true (default parity with Network)", () => { renderWithMantine( - , + , ); expect( screen.getAllByRole("button", { name: "Expand" }).length, @@ -423,7 +423,7 @@ describe("HistoryListPanel", () => { it("renders entries expanded when compact is false", () => { renderWithMantine( - { const user = userEvent.setup(); const onToggleCompact = vi.fn(); renderWithMantine( - ; searchText: string; @@ -35,9 +35,9 @@ export interface HistoryListPanelProps { onClearAll: () => void; onExport: () => void; /** Clear just one section's entries (pinned vs unpinned history). */ - onClearSection: (section: HistorySectionName) => void; + onClearSection: (section: ProtocolSectionName) => void; /** Export just one section's entries. */ - onExportSection: (section: HistorySectionName) => void; + onExportSection: (section: ProtocolSectionName) => void; onReplay: (id: string) => void; onTogglePin: (id: string) => void; sortDirection: SortDirection; @@ -91,21 +91,26 @@ const SectionActionGroup = Group.withProps({ }); // Subtle link-style button, matching the Select/Deselect All control in -// HistoryControls. +// ProtocolControls. const SectionLinkButton = Button.withProps({ variant: "subtle", size: "xs", }); +// The two in-panel sub-section labels. The un-pinned section deliberately keeps +// the "History" wording (and the `"history"` discriminator below) even though +// the tab/feature was renamed to "Protocol" — this is the settled boundary from +// #1623: only the tab/feature renames; the section discriminator and its +// Pinned / History labels stay. function formatPinnedTitle(count: number): string { - return `Pinned Requests (${count})`; + return `Pinned Messages (${count})`; } function formatHistoryTitle(count: number): string { return `History (${count})`; } -type HistorySectionName = "pinned" | "history"; +type ProtocolSectionName = "pinned" | "history"; // Per-section Clear / Export links, shown to the right of a section header when // both sections are present (so each can be cleared/exported on its own). @@ -130,7 +135,7 @@ function SectionActions({ // sense: the header is a plain title and the entries always show (so a stale // collapsed state from when both sections were present can't hide them) — // unless `hideHeaderWhenAlone`, in which case the lone section drops its title -// entirely (the "History" label is redundant when there's nothing to +// entirely (the "Protocol" label is redundant when there's nothing to // distinguish it from). function CollapsibleSection({ title, @@ -200,7 +205,7 @@ function matchesFilters( return true; } -export function HistoryListPanel({ +export function ProtocolListPanel({ entries, pinnedIds, searchText, @@ -218,8 +223,8 @@ export function HistoryListPanel({ onToggleCompact, onPin, embedded = false, -}: HistoryListPanelProps) { - const viewportRef = useScrollMemory("history-list"); +}: ProtocolListPanelProps) { + const viewportRef = useScrollMemory("protocol-list"); // Per-section expand/collapse, like the LogControls level toggles. Both start // open; collapsing hides that section's entries without affecting the other. const [pinnedOpen, setPinnedOpen] = useState(true); @@ -268,7 +273,7 @@ export function HistoryListPanel({ return ( - Requests + Messages {pinnedEntries.map((entry) => ( - {unpinnedEntries.map((entry) => ( - { it("returns the method name for a request entry", () => { diff --git a/clients/web/src/components/groups/historyUtils.ts b/clients/web/src/components/groups/protocolUtils.ts similarity index 63% rename from clients/web/src/components/groups/historyUtils.ts rename to clients/web/src/components/groups/protocolUtils.ts index e418caf1..5b85b0db 100644 --- a/clients/web/src/components/groups/historyUtils.ts +++ b/clients/web/src/components/groups/protocolUtils.ts @@ -10,14 +10,14 @@ export function extractMethod(entry: MessageEntry): MessageMethod { } /** - * Request methods the History Replay action can re-issue (client→server reads + * Request methods the Protocol Replay action can re-issue (client→server reads * and calls). Server→client requests (roots/list, sampling, elicitation) and * side-effectful methods (logging/setLevel, subscribe) are intentionally - * excluded. Single source of truth: `HistoryEntry` hides the Replay button for - * anything not listed here, and App's `replayHistoryRequest` gates dispatch on + * excluded. Single source of truth: `ProtocolEntry` hides the Replay button for + * anything not listed here, and App's `replayProtocolRequest` gates dispatch on * the same set. */ -export const REPLAYABLE_HISTORY_METHODS: ReadonlySet = new Set([ +export const REPLAYABLE_PROTOCOL_METHODS: ReadonlySet = new Set([ "tools/call", "prompts/get", "resources/read", @@ -29,6 +29,6 @@ export const REPLAYABLE_HISTORY_METHODS: ReadonlySet = new Set([ "ping", ]); -export function isReplayableHistoryMethod(method: string): boolean { - return REPLAYABLE_HISTORY_METHODS.has(method); +export function isReplayableProtocolMethod(method: string): boolean { + return REPLAYABLE_PROTOCOL_METHODS.has(method); } diff --git a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx b/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.stories.tsx similarity index 84% rename from clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx rename to clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.stories.tsx index b7e23c03..0c24e62d 100644 --- a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx +++ b/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.stories.tsx @@ -3,21 +3,21 @@ import type { ComponentProps } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { expect, fn, screen, userEvent, within } from "storybook/test"; -import { HistoryScreen } from "./HistoryScreen"; -import { EMPTY_HISTORY_UI } from "../screenUiState"; +import { ProtocolScreen } from "./ProtocolScreen"; +import { EMPTY_PROTOCOL_UI } from "../screenUiState"; -// HistoryScreen is controlled (search text + method filter live in the parent +// ProtocolScreen is controlled (search text + method filter live in the parent // as one `ui` object — see #1417). This wrapper holds that state so the // play-driven filter selection and clear-all reset are observable, mirroring // how App owns the state. -function StatefulHistoryScreen(args: ComponentProps) { - const [ui, setUi] = useState({ ...EMPTY_HISTORY_UI, ...args.ui }); - return ; +function StatefulProtocolScreen(args: ComponentProps) { + const [ui, setUi] = useState({ ...EMPTY_PROTOCOL_UI, ...args.ui }); + return ; } -const meta: Meta = { - title: "Screens/HistoryScreen", - component: HistoryScreen, +const meta: Meta = { + title: "Screens/ProtocolScreen", + component: ProtocolScreen, parameters: { layout: "fullscreen" }, args: { pinnedIds: new Set(), @@ -25,18 +25,18 @@ const meta: Meta = { onExport: fn(), onReplay: fn(), onTogglePin: fn(), - ui: EMPTY_HISTORY_UI, + ui: EMPTY_PROTOCOL_UI, onUiChange: fn(), sortDirection: "newest-first", onSortChange: fn(), compact: true, onToggleCompact: fn(), }, - render: (args) => , + render: (args) => , }; export default meta; -type Story = StoryObj; +type Story = StoryObj; const sampleEntries: MessageEntry[] = [ { diff --git a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.test.tsx b/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.test.tsx similarity index 75% rename from clients/web/src/components/screens/HistoryScreen/HistoryScreen.test.tsx rename to clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.test.tsx index f5ae4f10..00ba2ae1 100644 --- a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.test.tsx +++ b/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.test.tsx @@ -2,8 +2,8 @@ import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { MessageEntry } from "@inspector/core/mcp/types.js"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; -import { HistoryScreen } from "./HistoryScreen"; -import { EMPTY_HISTORY_UI } from "../screenUiState"; +import { ProtocolScreen } from "./ProtocolScreen"; +import { EMPTY_PROTOCOL_UI } from "../screenUiState"; const sampleEntries: MessageEntry[] = [ { @@ -23,7 +23,7 @@ const sampleEntries: MessageEntry[] = [ const baseProps = { entries: sampleEntries, pinnedIds: new Set(), - ui: EMPTY_HISTORY_UI, + ui: EMPTY_PROTOCOL_UI, onUiChange: vi.fn(), onClearAll: vi.fn(), onExport: vi.fn(), @@ -37,22 +37,24 @@ const baseProps = { onToggleCompact: vi.fn(), }; -describe("HistoryScreen", () => { +describe("ProtocolScreen", () => { it("renders the controls and panel", () => { - renderWithMantine(); - expect(screen.getByText("History")).toBeInTheDocument(); + renderWithMantine(); + expect(screen.getByText("Protocol")).toBeInTheDocument(); expect(screen.getByPlaceholderText("Search...")).toBeInTheDocument(); }); it("renders empty state when there are no entries", () => { - renderWithMantine(); - expect(screen.getByText("History")).toBeInTheDocument(); + renderWithMantine(); + expect(screen.getByText("Protocol")).toBeInTheDocument(); }); it("invokes onClearAll when clear is triggered", async () => { const user = userEvent.setup(); const onClearAll = vi.fn(); - renderWithMantine(); + renderWithMantine( + , + ); const clearButton = screen.getByRole("button", { name: /Clear/ }); await user.click(clearButton); expect(onClearAll).toHaveBeenCalled(); @@ -61,7 +63,9 @@ describe("HistoryScreen", () => { it("emits the search text through onUiChange", async () => { const user = userEvent.setup(); const onUiChange = vi.fn(); - renderWithMantine(); + renderWithMantine( + , + ); await user.type(screen.getByPlaceholderText("Search..."), "t"); expect(onUiChange).toHaveBeenCalledWith( expect.objectContaining({ search: "t" }), @@ -71,8 +75,10 @@ describe("HistoryScreen", () => { it("toggles a single message direction through onUiChange", async () => { const user = userEvent.setup(); const onUiChange = vi.fn(); - renderWithMantine(); - await user.click(screen.getByRole("button", { name: "client ← server" })); + renderWithMantine( + , + ); + await user.click(screen.getByRole("button", { name: "server → client" })); expect(onUiChange).toHaveBeenCalledWith( expect.objectContaining({ visibleDirections: { client: true, server: false }, @@ -83,7 +89,9 @@ describe("HistoryScreen", () => { it("toggles all message directions off through onUiChange", async () => { const user = userEvent.setup(); const onUiChange = vi.fn(); - renderWithMantine(); + renderWithMantine( + , + ); await user.click(screen.getByRole("button", { name: "Deselect All" })); expect(onUiChange).toHaveBeenCalledWith( expect.objectContaining({ @@ -96,9 +104,9 @@ describe("HistoryScreen", () => { const user = userEvent.setup(); const onUiChange = vi.fn(); const { container } = renderWithMantine( - , ); @@ -115,24 +123,24 @@ describe("HistoryScreen", () => { it("renders a pin-as-column button when onPin is provided and invokes it", async () => { const user = userEvent.setup(); const onPin = vi.fn(); - renderWithMantine(); + renderWithMantine(); await user.click(screen.getByRole("button", { name: "Pin as column" })); expect(onPin).toHaveBeenCalledTimes(1); }); it("drops the filter sidebar when embedded, keeping the request list", () => { - renderWithMantine(); - expect(screen.getByText("Requests")).toBeInTheDocument(); - // The sidebar (HistoryControls, with its Search box) is not rendered. + renderWithMantine(); + expect(screen.getByText("Messages")).toBeInTheDocument(); + // The sidebar (ProtocolControls, with its Search box) is not rendered. expect(screen.queryByPlaceholderText("Search...")).toBeNull(); }); it("applies the search text but ignores the method filter when embedded", () => { renderWithMantine( - { it("hides entries not matching the column search when embedded", () => { renderWithMantine( - , ); diff --git a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx b/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.tsx similarity index 88% rename from clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx rename to clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.tsx index 5f269b5b..baa13b47 100644 --- a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx +++ b/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.tsx @@ -5,16 +5,16 @@ import type { MessageMethod, MessageOrigin, } from "@inspector/core/mcp/types.js"; -import { HistoryControls } from "../../groups/HistoryControls/HistoryControls"; -import { HistoryListPanel } from "../../groups/HistoryListPanel/HistoryListPanel.js"; -import { extractMethod } from "../../groups/historyUtils.js"; +import { ProtocolControls } from "../../groups/ProtocolControls/ProtocolControls"; +import { ProtocolListPanel } from "../../groups/ProtocolListPanel/ProtocolListPanel.js"; +import { extractMethod } from "../../groups/protocolUtils.js"; import type { SortDirection } from "../../elements/SortToggle/SortToggle"; -export interface HistoryScreenProps { +export interface ProtocolScreenProps { entries: MessageEntry[]; pinnedIds: Set; - ui: HistoryUiState; - onUiChange: (next: HistoryUiState) => void; + ui: ProtocolUiState; + onUiChange: (next: ProtocolUiState) => void; onClearAll: () => void; onExport: () => void; onClearSection: (section: "pinned" | "history") => void; @@ -34,7 +34,7 @@ export interface HistoryScreenProps { // Search text, method filter, and per-direction visibility — controlled by the // parent (App) as one object so they persist across tab navigation within a // live session (#1417). -export interface HistoryUiState { +export interface ProtocolUiState { search: string; methodFilter?: MessageMethod; /** Which message directions are shown, keyed by entry origin. */ @@ -58,7 +58,7 @@ const SidebarCard = Card.withProps({ padding: "lg", }); -export function HistoryScreen({ +export function ProtocolScreen({ entries, pinnedIds, ui, @@ -75,7 +75,7 @@ export function HistoryScreen({ onToggleCompact, onPin, embedded = false, -}: HistoryScreenProps) { +}: ProtocolScreenProps) { const { search, methodFilter, visibleDirections } = ui; const availableMethods = useMemo( @@ -111,7 +111,7 @@ export function HistoryScreen({ {embedded ? null : ( - )} - { expectScrollbarGutterIdleHidden(canvasElement); }, diff --git a/clients/web/src/components/screens/ServerListScreen/ServerListScreen.tsx b/clients/web/src/components/screens/ServerListScreen/ServerListScreen.tsx index 9b903f77..f27b0d33 100644 --- a/clients/web/src/components/screens/ServerListScreen/ServerListScreen.tsx +++ b/clients/web/src/components/screens/ServerListScreen/ServerListScreen.tsx @@ -200,7 +200,7 @@ export function ServerListScreen({ = { logs: demoLogs, tasks: demoTasks, progressByTaskId: demoProgressByTaskId, - history: demoHistory, + protocol: demoHistory, network: demoNetwork, stderrLogs: demoStderr, @@ -360,7 +360,7 @@ const meta: Meta = { appsUi: EMPTY_APPS_UI, tasksUi: EMPTY_TASKS_UI, logsUi: EMPTY_LOGS_UI, - historyUi: EMPTY_HISTORY_UI, + protocolUi: EMPTY_PROTOCOL_UI, networkUi: EMPTY_NETWORK_UI, consoleUi: EMPTY_CONSOLE_UI, @@ -401,11 +401,11 @@ const meta: Meta = { onLogsUiChange: fn(), onClearLogs: fn(), onExportLogs: fn(), - onHistoryUiChange: fn(), - onClearHistory: fn(), - onExportHistory: fn(), - onReplayHistory: fn(), - onTogglePinHistory: fn(), + onProtocolUiChange: fn(), + onClearProtocol: fn(), + onExportProtocol: fn(), + onReplayProtocol: fn(), + onTogglePinProtocol: fn(), onNetworkUiChange: fn(), onClearNetwork: fn(), onExportNetwork: fn(), @@ -511,12 +511,12 @@ export const Connected: Story = { }; export const ConnectionError: Story = { - // demoServers[0] is a stdio server; a failed launch has an empty History (the + // demoServers[0] is a stdio server; a failed launch has an empty Protocol (the // message log clears on the error transition) and no HTTP traffic, so the // auto-opened column keys off the captured stderr and leads with Console — // the process's startup error (#1621). `erroredServerId` is the parent's // connect-attempt-failure signal that gates the failure column; `network: []` - // (and empty History) keep it a pure stdio failure so only Console is offered. + // (and empty Protocol) keep it a pure stdio failure so only Console is offered. args: { activeServer: demoServers[0]!.id, erroredServerId: demoServers[0]!.id, diff --git a/clients/web/src/components/views/InspectorView/InspectorView.test.tsx b/clients/web/src/components/views/InspectorView/InspectorView.test.tsx index d5eb9c29..ab2922ad 100644 --- a/clients/web/src/components/views/InspectorView/InspectorView.test.tsx +++ b/clients/web/src/components/views/InspectorView/InspectorView.test.tsx @@ -42,7 +42,7 @@ import { EMPTY_RESOURCES_UI, EMPTY_TASKS_UI, EMPTY_LOGS_UI, - EMPTY_HISTORY_UI, + EMPTY_PROTOCOL_UI, EMPTY_NETWORK_UI, EMPTY_CONSOLE_UI, } from "../../screens/screenUiState"; @@ -83,7 +83,7 @@ function makeProps( subscriptions: [], logs: [], tasks: [], - history: [], + protocol: [], network: [], stderrLogs: [], currentLogLevel: "info", @@ -96,7 +96,7 @@ function makeProps( appsUi: EMPTY_APPS_UI, tasksUi: EMPTY_TASKS_UI, logsUi: EMPTY_LOGS_UI, - historyUi: EMPTY_HISTORY_UI, + protocolUi: EMPTY_PROTOCOL_UI, networkUi: EMPTY_NETWORK_UI, consoleUi: EMPTY_CONSOLE_UI, onToggleTheme: vi.fn(), @@ -133,13 +133,13 @@ function makeProps( onLogsUiChange: vi.fn(), onClearLogs: vi.fn(), onExportLogs: vi.fn(), - onHistoryUiChange: vi.fn(), - onClearHistory: vi.fn(), - onExportHistory: vi.fn(), - onClearHistorySection: vi.fn(), - onExportHistorySection: vi.fn(), - onReplayHistory: vi.fn(), - onTogglePinHistory: vi.fn(), + onProtocolUiChange: vi.fn(), + onClearProtocol: vi.fn(), + onExportProtocol: vi.fn(), + onClearProtocolSection: vi.fn(), + onExportProtocolSection: vi.fn(), + onReplayProtocol: vi.fn(), + onTogglePinProtocol: vi.fn(), onNetworkUiChange: vi.fn(), onClearNetwork: vi.fn(), onExportNetwork: vi.fn(), @@ -498,8 +498,8 @@ describe("InspectorView", () => { expect(radios.map((r) => r.getAttribute("value"))).toContain("Logs"); }); - it("keeps History available regardless of advertised server capabilities", async () => { - // History is a local client-side log — never gated on server capabilities. + it("keeps Protocol available regardless of advertised server capabilities", async () => { + // Protocol is a local client-side log — never gated on server capabilities. renderWithMantine( { const radios = await screen.findAllByRole("radio"); const labels = radios.map((r) => r.getAttribute("value")); expect(labels).toContain("Servers"); - expect(labels).toContain("History"); + expect(labels).toContain("Protocol"); expect(labels).not.toContain("Tools"); expect(labels).not.toContain("Logs"); }); @@ -925,7 +925,7 @@ describe("InspectorView", () => { it("falls back to newest-first when a corrupted sort value is stored", async () => { const user = userEvent.setup({ delay: null }); - window.localStorage.setItem("inspector.sortDirection.history", "garbage"); + window.localStorage.setItem("inspector.sortDirection.protocol", "garbage"); renderWithMantine( { ); const tabSelect = await screen.findByDisplayValue("Servers"); await user.click(tabSelect); - await user.click(await screen.findByText("History")); + await user.click(await screen.findByText("Protocol")); const sortSelect = await screen.findByRole("textbox", { name: "History sort direction", }); await waitFor(() => expect(sortSelect).toHaveValue("Newest First")); }); - it("persists History list compact state to localStorage and restores it on remount", async () => { + it("persists Protocol list compact state to localStorage and restores it on remount", async () => { const user = userEvent.setup({ delay: null }); const historyEntry = { id: "req-1", @@ -966,20 +966,20 @@ describe("InspectorView", () => { connectionStatus: "connected", initializeResult: connectedInit, latencyMs: 50, - history: [historyEntry], + protocol: [historyEntry], })} />, ); const tabSelect = await screen.findByDisplayValue("Servers"); await user.click(tabSelect); - await user.click(await screen.findByText("History")); + await user.click(await screen.findByText("Protocol")); // Default is collapsed — ListToggle reads "Expand all". await user.click(await screen.findByRole("button", { name: "Expand all" })); await waitFor(() => - expect(window.localStorage.getItem("inspector.listCompact.history")).toBe( - "false", - ), + expect( + window.localStorage.getItem("inspector.listCompact.protocol"), + ).toBe("false"), ); unmount(); @@ -991,13 +991,13 @@ describe("InspectorView", () => { connectionStatus: "connected", initializeResult: connectedInit, latencyMs: 50, - history: [historyEntry], + protocol: [historyEntry], })} />, ); const tabSelect2 = await screen.findByDisplayValue("Servers"); await user.click(tabSelect2); - await user.click(await screen.findByText("History")); + await user.click(await screen.findByText("Protocol")); // After restore the list is expanded, so the ListToggle reads "Collapse all". expect( await screen.findByRole("button", { name: "Collapse all" }), @@ -1006,7 +1006,7 @@ describe("InspectorView", () => { it("falls back to collapsed when a corrupted compact value is stored", async () => { const user = userEvent.setup({ delay: null }); - window.localStorage.setItem("inspector.listCompact.history", "garbage"); + window.localStorage.setItem("inspector.listCompact.protocol", "garbage"); const historyEntry = { id: "req-1", timestamp: new Date("2026-03-17T10:00:00Z"), @@ -1025,13 +1025,13 @@ describe("InspectorView", () => { connectionStatus: "connected", initializeResult: connectedInit, latencyMs: 50, - history: [historyEntry], + protocol: [historyEntry], })} />, ); const tabSelect = await screen.findByDisplayValue("Servers"); await user.click(tabSelect); - await user.click(await screen.findByText("History")); + await user.click(await screen.findByText("Protocol")); expect( await screen.findByRole("button", { name: "Expand all" }), ).toBeInTheDocument(); @@ -1437,12 +1437,12 @@ describe("InspectorView", () => { ).toBeInTheDocument(); // The failure column leads with Network (the captured request) — the only - // diagnostic with content. History is content-gated and empty here (the + // diagnostic with content. Protocol is content-gated and empty here (the // message log clears on the error transition), so it isn't offered; nor is // Logs (no logging capability was negotiated) or Console (HTTP has no // child-process stderr). expect(screen.getByRole("radio", { name: "Network" })).toBeChecked(); - expect(screen.queryByRole("radio", { name: "History" })).toBeNull(); + expect(screen.queryByRole("radio", { name: "Protocol" })).toBeNull(); expect(screen.queryByRole("radio", { name: "Logs" })).toBeNull(); expect(screen.queryByRole("radio", { name: "Console" })).toBeNull(); }); @@ -1480,21 +1480,21 @@ describe("InspectorView", () => { ); // The captured stderr means it was a stdio launch: the column leads with // Console (the process's stderr) — that's where the spawn error is — with - // no Network (no HTTP traffic) and no History (content-gated, empty on a + // no Network (no HTTP traffic) and no Protocol (content-gated, empty on a // fresh failure). The stderr line renders. expect( await screen.findByRole("radio", { name: "Console" }), ).toBeChecked(); - expect(screen.queryByRole("radio", { name: "History" })).toBeNull(); + expect(screen.queryByRole("radio", { name: "Protocol" })).toBeNull(); expect(screen.queryByRole("radio", { name: "Network" })).toBeNull(); expect(screen.getByText("ModuleNotFoundError: boom")).toBeInTheDocument(); }); - it("leads with Console even when the stored tab was History, on a stdio failure (#1621)", async () => { + it("leads with Console even when the stored tab was Protocol, on a stdio failure (#1621)", async () => { // Regression for the review note: a returning user whose last-pinned tab - // was History must still land on the Console diagnostic, not the (empty) - // History — History is content-gated out of the failure column. - window.localStorage.setItem("inspector.monitor.tab", "History"); + // was Protocol must still land on the Console diagnostic, not the (empty) + // Protocol — Protocol is content-gated out of the failure column. + window.localStorage.setItem("inspector.monitor.tab", "Protocol"); monitorWide.value = true; const stdioErr: ServerEntry = { id: "beta", @@ -1525,12 +1525,12 @@ describe("InspectorView", () => { expect( await screen.findByRole("radio", { name: "Console" }), ).toBeChecked(); - expect(screen.queryByRole("radio", { name: "History" })).toBeNull(); + expect(screen.queryByRole("radio", { name: "Protocol" })).toBeNull(); }); it("keeps the failure column closed until a diagnostic has content (#1621)", () => { // A connect failure with nothing captured yet (no stderr, no fetch, and - // History cleared on the error transition) opens onto nothing — so the + // Protocol cleared on the error transition) opens onto nothing — so the // column stays closed rather than showing an empty pane. monitorWide.value = true; const { rerender } = renderWithMantine( @@ -1544,7 +1544,7 @@ describe("InspectorView", () => { ); rerender( , ); expect( @@ -1552,8 +1552,8 @@ describe("InspectorView", () => { ).toBeNull(); }); - it("offers History in the failure column when it has captured content (#1621)", async () => { - // The failure History tab is content-gated, so when the message log *does* + it("offers Protocol in the failure column when it has captured content (#1621)", async () => { + // The failure Protocol tab is content-gated, so when the message log *does* // hold entries it's offered alongside the diagnostic (Network here). monitorWide.value = true; const { rerender } = renderWithMantine( @@ -1579,7 +1579,7 @@ describe("InspectorView", () => { category: "transport", }, ], - history: [ + protocol: [ { id: "h1", timestamp: new Date(), @@ -1594,7 +1594,7 @@ describe("InspectorView", () => { await screen.findByRole("radio", { name: "Network" }), ).toBeChecked(); expect( - screen.getByRole("radio", { name: "History" }), + screen.getByRole("radio", { name: "Protocol" }), ).toBeInTheDocument(); }); @@ -1664,13 +1664,13 @@ describe("InspectorView", () => { })} />, ); - // Pinned column for a stdio server hosts Logs/History/Console — never + // Pinned column for a stdio server hosts Logs/Protocol/Console — never // Network (no HTTP traffic to show). expect( await screen.findByRole("radio", { name: "Console" }), ).toBeInTheDocument(); expect( - screen.getByRole("radio", { name: "History" }), + screen.getByRole("radio", { name: "Protocol" }), ).toBeInTheDocument(); expect(screen.queryByRole("radio", { name: "Network" })).toBeNull(); }); @@ -1717,7 +1717,7 @@ describe("InspectorView", () => { const header = screen.getByRole("banner"); expect(within(header).queryByRole("radio", { name: "Logs" })).toBeNull(); expect( - within(header).queryByRole("radio", { name: "History" }), + within(header).queryByRole("radio", { name: "Protocol" }), ).toBeNull(); expect( within(header).queryByRole("radio", { name: "Network" }), @@ -1770,14 +1770,14 @@ describe("InspectorView", () => { within(header).getByRole("radio", { name: "Tools" }), ).toBeChecked(); - // Switch the column to History, then close it. - await user.click(await screen.findByRole("radio", { name: "History" })); + // Switch the column to Protocol, then close it. + await user.click(await screen.findByRole("radio", { name: "Protocol" })); await user.click( await screen.findByRole("button", { name: "Close monitoring column" }), ); // The primary stays on the header's current screen (Tools), not the - // column's History, and the monitor group returns to the header. + // column's Protocol, and the monitor group returns to the header. await waitFor(() => expect( screen.queryByRole("button", { name: "Close monitoring column" }), @@ -1787,7 +1787,7 @@ describe("InspectorView", () => { within(header).getByRole("radio", { name: "Tools" }), ).toBeChecked(); expect( - within(header).getByRole("radio", { name: "History" }), + within(header).getByRole("radio", { name: "Protocol" }), ).toBeInTheDocument(); }); @@ -1898,7 +1898,7 @@ describe("InspectorView", () => { }); it("clamps the primary tab to Servers when the header has no other tab", () => { - // stdio + logging only ⇒ availableTabs = [Servers, Logs, History]; pinning + // stdio + logging only ⇒ availableTabs = [Servers, Logs, Protocol]; pinning // moves both monitor tabs out, leaving [Servers] as the only header tab. window.localStorage.setItem("inspector.monitor.pinned", "true"); monitorWide.value = true; @@ -1928,10 +1928,10 @@ describe("InspectorView", () => { monitorWide.value = true; const user = userEvent.setup(); renderWithMantine(); - await user.click(await screen.findByRole("radio", { name: "History" })); + await user.click(await screen.findByRole("radio", { name: "Protocol" })); await waitFor(() => expect(window.localStorage.getItem("inspector.monitor.tab")).toBe( - "History", + "Protocol", ), ); }); @@ -1949,7 +1949,7 @@ describe("InspectorView", () => { params: { level: "info", data: "loghello" }, }, ], - history: [ + protocol: [ { id: "h1", timestamp: new Date(), @@ -1968,8 +1968,8 @@ describe("InspectorView", () => { await user.type(searchBox, "zzz"); expect(screen.queryByText("loghello")).toBeNull(); - // ...and the same term carries over to History (still filtered → empty). - await user.click(screen.getByRole("radio", { name: "History" })); + // ...and the same term carries over to Protocol (still filtered → empty). + await user.click(screen.getByRole("radio", { name: "Protocol" })); expect(screen.getByText("No request history")).toBeInTheDocument(); expect(screen.getByRole("textbox", { name: "Search" })).toHaveValue( "zzz", diff --git a/clients/web/src/components/views/InspectorView/InspectorView.tsx b/clients/web/src/components/views/InspectorView/InspectorView.tsx index e9c8bbc5..b2a655d1 100644 --- a/clients/web/src/components/views/InspectorView/InspectorView.tsx +++ b/clients/web/src/components/views/InspectorView/InspectorView.tsx @@ -72,9 +72,9 @@ import { } from "../../screens/TasksScreen/TasksScreen"; import type { TaskProgress } from "../../groups/TaskCard/TaskCard"; import { - HistoryScreen, - type HistoryUiState, -} from "../../screens/HistoryScreen/HistoryScreen"; + ProtocolScreen, + type ProtocolUiState, +} from "../../screens/ProtocolScreen/ProtocolScreen"; import { NetworkScreen, type NetworkUiState, @@ -126,7 +126,7 @@ function serializeListCompact(value: boolean): string { // SPA only, no SSR — so the persisted value lands without a one-frame // flicker through the default. The `inspector..` namespace // keeps related preferences grouped and easy to clear in bulk. -function useSortDirection(scope: "logs" | "history" | "network" | "console") { +function useSortDirection(scope: "logs" | "protocol" | "network" | "console") { return useLocalStorage({ key: `inspector.sortDirection.${scope}`, defaultValue: SORT_DEFAULT, @@ -137,7 +137,7 @@ function useSortDirection(scope: "logs" | "history" | "network" | "console") { } function useListCompact( - scope: "history" | "network" | "servers" | "resources", + scope: "protocol" | "network" | "servers" | "resources", defaultValue: boolean, ) { return useLocalStorage({ @@ -151,7 +151,7 @@ function useListCompact( const SERVERS_TAB = INSPECTOR_SERVERS_TAB; const LOGS_TAB = "Logs"; -const HISTORY_TAB = "History"; +const PROTOCOL_TAB = "Protocol"; const NETWORK_TAB = "Network"; const CONSOLE_TAB = "Console"; @@ -163,7 +163,7 @@ const ALL_TABS: string[] = [ "Resources", "Tasks", LOGS_TAB, - HISTORY_TAB, + PROTOCOL_TAB, NETWORK_TAB, CONSOLE_TAB, ]; @@ -173,10 +173,10 @@ const ALL_TABS: string[] = [ // the header and hosts them in the column instead. Console (#1621) is the // stdio server's stderr stream — mutually exclusive with Network (Console shows // for stdio, Network for HTTP), but both live in the monitor group. -type MonitorTab = "Logs" | "History" | "Network" | "Console"; +type MonitorTab = "Logs" | "Protocol" | "Network" | "Console"; const MONITOR_TABS: string[] = [ LOGS_TAB, - HISTORY_TAB, + PROTOCOL_TAB, NETWORK_TAB, CONSOLE_TAB, ]; @@ -184,7 +184,7 @@ const MONITOR_TABS: string[] = [ function isMonitorTab(tab: string): tab is MonitorTab { return ( tab === LOGS_TAB || - tab === HISTORY_TAB || + tab === PROTOCOL_TAB || tab === NETWORK_TAB || tab === CONSOLE_TAB ); @@ -377,7 +377,7 @@ export interface InspectorViewProps { logs: LogEntryData[]; tasks: Task[]; progressByTaskId?: Record; - history: MessageEntry[]; + protocol: MessageEntry[]; network: FetchRequestEntry[]; /** Captured stdio stderr (the Console screen). Empty for HTTP servers. (#1621) */ stderrLogs: StderrLogEntry[]; @@ -398,7 +398,7 @@ export interface InspectorViewProps { appsUi: AppsUiState; tasksUi: TasksUiState; logsUi: LogsUiState; - historyUi: HistoryUiState; + protocolUi: ProtocolUiState; networkUi: NetworkUiState; consoleUi: ConsoleUiState; @@ -418,9 +418,9 @@ export interface InspectorViewProps { bridgeFactory: BridgeFactory; appRendererRef: Ref; - // History pinning. Optional because pin state isn't persisted yet (#1244 + // Protocol pinning. Optional because pin state isn't persisted yet (#1244 // is single-PR; persistence is a separate concern). - pinnedHistoryIds?: Set; + pinnedProtocolIds?: Set; // Theme toggle (lives in the parent so the color scheme can also flow // into other top-level UI later). @@ -505,13 +505,13 @@ export interface InspectorViewProps { onClearLogs: () => void; onExportLogs: () => void; - onHistoryUiChange: (next: HistoryUiState) => void; - onClearHistory: () => void; - onExportHistory: () => void; - onClearHistorySection: (section: "pinned" | "history") => void; - onExportHistorySection: (section: "pinned" | "history") => void; - onReplayHistory: (id: string) => void; - onTogglePinHistory: (id: string) => void; + onProtocolUiChange: (next: ProtocolUiState) => void; + onClearProtocol: () => void; + onExportProtocol: () => void; + onClearProtocolSection: (section: "pinned" | "history") => void; + onExportProtocolSection: (section: "pinned" | "history") => void; + onReplayProtocol: (id: string) => void; + onTogglePinProtocol: (id: string) => void; onNetworkUiChange: (next: NetworkUiState) => void; onClearNetwork: () => void; @@ -548,7 +548,7 @@ export function InspectorView({ logs, tasks, progressByTaskId, - history, + protocol, network, stderrLogs, toolCallState, @@ -560,14 +560,14 @@ export function InspectorView({ appsUi, tasksUi, logsUi, - historyUi, + protocolUi, networkUi, consoleUi, currentLogLevel, sandboxPath, bridgeFactory, appRendererRef, - pinnedHistoryIds, + pinnedProtocolIds, onToggleTheme, onOpenClientSettings, onToggleConnection, @@ -611,13 +611,13 @@ export function InspectorView({ onLogsUiChange, onClearLogs, onExportLogs, - onHistoryUiChange, - onClearHistory, - onExportHistory, - onClearHistorySection, - onExportHistorySection, - onReplayHistory, - onTogglePinHistory, + onProtocolUiChange, + onClearProtocol, + onExportProtocol, + onClearProtocolSection, + onExportProtocolSection, + onReplayProtocol, + onTogglePinProtocol, onNetworkUiChange, onClearNetwork, onExportNetwork, @@ -638,16 +638,16 @@ export function InspectorView({ // toggles (sort direction, list compact). Tab selection is lifted (#1417). const [logsSort, setLogsSort] = useSortDirection("logs"); - const [historySort, setHistorySort] = useSortDirection("history"); + const [protocolSort, setProtocolSort] = useSortDirection("protocol"); const [networkSort, setNetworkSort] = useSortDirection("network"); const [consoleSort, setConsoleSort] = useSortDirection("console"); // Servers and Resources default to expanded (collapsed=false) so new - // users see content on first paint; History/Network default to + // users see content on first paint; Protocol/Network default to // collapsed (the lists are long enough that compact is the better // first-paint state). - const [historyCompact, setHistoryCompact] = useListCompact( - "history", + const [protocolCompact, setProtocolCompact] = useListCompact( + "protocol", LIST_COMPACT_DEFAULT, ); const [networkCompact, setNetworkCompact] = useListCompact( @@ -660,7 +660,7 @@ export function InspectorView({ false, ); - // Monitoring-column state (#1616): whether Logs/History/Network are pinned to + // Monitoring-column state (#1616): whether Logs/Protocol/Network are pinned to // the right column, which one is active there, and the column width. All // persisted (view-layout preferences), same shape as the sort/compact hooks. const [monitorPinned, setMonitorPinned] = useLocalStorage({ @@ -761,7 +761,7 @@ export function InspectorView({ // its "unavailable" message remains reachable. // // Network is hidden for stdio servers (no HTTP traffic to surface). - // Servers and History are never capability-gated — History is a local + // Servers and Protocol are never capability-gated — Protocol is a local // client-side log, and any future client capabilities (sampling / // elicitation / roots) are inspector-offered, not server-advertised, so // they must not be gated here either. These memo dependencies make the tabs @@ -824,7 +824,7 @@ export function InspectorView({ // • stdio → the process's stderr (Console): the spawn/startup error the // child printed before dying. // • HTTP → the failed requests (Network). - // • History only if it has entries — the message log is cleared on the + // • Protocol only if it has entries — the message log is cleared on the // error transition, so on a fresh connect failure it's empty; offering // it anyway would let an empty tab lead over (and hide) the real // diagnostic. Content-gating it keeps the actual diagnostic first. @@ -835,11 +835,11 @@ export function InspectorView({ const tabs: string[] = []; if (stderrLogs.length > 0) tabs.push(CONSOLE_TAB); if (network.length > 0) tabs.push(NETWORK_TAB); - if (history.length > 0) tabs.push(HISTORY_TAB); + if (protocol.length > 0) tabs.push(PROTOCOL_TAB); return tabs; } return []; - }, [connected, failed, availableTabs, stderrLogs, network, history]); + }, [connected, failed, availableTabs, stderrLogs, network, protocol]); const effectivePinned = monitorPinned && !!isWide && @@ -907,7 +907,7 @@ export function InspectorView({ } // A single search shared across the column's tabs (kept as you move between - // Logs/History/Network so it filters each one), distinct from the full-size + // Logs/Protocol/Network so it filters each one), distinct from the full-size // screens' own searches. The embedded panels apply only this text; their other // filters (levels / categories / directions / method) have no control in the // column and are bypassed there. @@ -975,21 +975,21 @@ export function InspectorView({ sortDirection: logsSort, onSortChange: setLogsSort, }; - const historyScreenProps = { - entries: history, - pinnedIds: pinnedHistoryIds ?? new Set(), - ui: historyUi, - onUiChange: onHistoryUiChange, - onClearAll: onClearHistory, - onExport: onExportHistory, - onClearSection: onClearHistorySection, - onExportSection: onExportHistorySection, - onReplay: onReplayHistory, - onTogglePin: onTogglePinHistory, - sortDirection: historySort, - onSortChange: setHistorySort, - compact: historyCompact, - onToggleCompact: () => setHistoryCompact((c) => !c), + const protocolScreenProps = { + entries: protocol, + pinnedIds: pinnedProtocolIds ?? new Set(), + ui: protocolUi, + onUiChange: onProtocolUiChange, + onClearAll: onClearProtocol, + onExport: onExportProtocol, + onClearSection: onClearProtocolSection, + onExportSection: onExportProtocolSection, + onReplay: onReplayProtocol, + onTogglePin: onTogglePinProtocol, + sortDirection: protocolSort, + onSortChange: setProtocolSort, + compact: protocolCompact, + onToggleCompact: () => setProtocolCompact((c) => !c), }; const networkScreenProps = { entries: network, @@ -1024,10 +1024,10 @@ export function InspectorView({ embedded /> ), - [HISTORY_TAB]: ( - ), @@ -1185,10 +1185,10 @@ export function InspectorView({ onPin={canPin ? () => pinMonitor(LOGS_TAB) : undefined} /> - - pinMonitor(HISTORY_TAB) : undefined} + + pinMonitor(PROTOCOL_TAB) : undefined} /> diff --git a/clients/web/src/lib/downloadFile.ts b/clients/web/src/lib/downloadFile.ts index 9cc71220..5c5d3f70 100644 --- a/clients/web/src/lib/downloadFile.ts +++ b/clients/web/src/lib/downloadFile.ts @@ -75,9 +75,9 @@ export function isHttpUrl(url: string): URL | null { * stable on-disk filename prefix. */ export type ExportKind = - | "history" - | "history-pinned" - | "history-unpinned" + | "protocol" + | "protocol-pinned" + | "protocol-unpinned" | "logs" | "network" | "console"; diff --git a/clients/web/src/test/lib/downloadFile.test.ts b/clients/web/src/test/lib/downloadFile.test.ts index 36cc656d..18c045b1 100644 --- a/clients/web/src/test/lib/downloadFile.test.ts +++ b/clients/web/src/test/lib/downloadFile.test.ts @@ -118,8 +118,8 @@ describe("buildExportFilename", () => { const fixedNow = new Date("2026-03-17T10:00:42.123Z"); it("includes kind, server id, and ISO timestamp with `:` swapped for `-`", () => { - expect(buildExportFilename("history", "alpha", fixedNow)).toBe( - "inspector-history-alpha-2026-03-17T10-00-42.123Z.json", + expect(buildExportFilename("protocol", "alpha", fixedNow)).toBe( + "inspector-protocol-alpha-2026-03-17T10-00-42.123Z.json", ); }); @@ -142,9 +142,9 @@ describe("buildExportFilename", () => { }); it("defaults `now` to the current time when not provided", () => { - const name = buildExportFilename("history", "alpha"); + const name = buildExportFilename("protocol", "alpha"); expect(name).toMatch( - /^inspector-history-alpha-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z\.json$/, + /^inspector-protocol-alpha-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z\.json$/, ); }); }); diff --git a/clients/web/src/test/scrollAreaStoryAssertions.ts b/clients/web/src/test/scrollAreaStoryAssertions.ts index a78a8969..cd3fe28e 100644 --- a/clients/web/src/test/scrollAreaStoryAssertions.ts +++ b/clients/web/src/test/scrollAreaStoryAssertions.ts @@ -4,7 +4,7 @@ import { expect } from "storybook/test"; * Assert a list `ScrollArea.Autosize` reserves a scrollbar gutter * (`offsetScrollbars`, so the bar never overlays the cards) and keeps the bar * hidden when idle (`type="scroll"`, so it shows only while scrolling rather - * than on hover). Shared by the History/Network/Logging panel stories (#1474). + * than on hover). Shared by the Protocol/Network/Logging panel stories (#1474). */ export function expectScrollbarGutterIdleHidden(canvasElement: HTMLElement) { const viewport = canvasElement.querySelector(".mantine-ScrollArea-viewport"); diff --git a/clients/web/src/theme/Accordion.ts b/clients/web/src/theme/Accordion.ts index 44e579f6..adec35d1 100644 --- a/clients/web/src/theme/Accordion.ts +++ b/clients/web/src/theme/Accordion.ts @@ -9,7 +9,7 @@ export const ThemeAccordion = Accordion.extend({ // scrolls within its own (item-count-weighted) share of the space, so // nothing scrolls until the panel is full. // - `filter-toggle` on the control gives the section headers the same - // outline-on-hover treatment as the FilterToggleButton and the History + // outline-on-hover treatment as the FilterToggleButton and the Protocol // section headers: a thin border on hover (rather than a background fill) // and a filled background when the section is open (`aria-expanded`). // Pair it with `chevron={}` and per-item `flex` weights. diff --git a/clients/web/src/types/navigation.ts b/clients/web/src/types/navigation.ts index bd708d83..e6078101 100644 --- a/clients/web/src/types/navigation.ts +++ b/clients/web/src/types/navigation.ts @@ -5,4 +5,4 @@ export type InspectorTab = | "resources" | "logs" | "tasks" - | "history"; + | "protocol"; diff --git a/clients/web/src/utils/inspectorTabs.test.ts b/clients/web/src/utils/inspectorTabs.test.ts index ebd7b696..db6ab064 100644 --- a/clients/web/src/utils/inspectorTabs.test.ts +++ b/clients/web/src/utils/inspectorTabs.test.ts @@ -19,7 +19,7 @@ describe("inspectorTabs", () => { "Resources", "Tasks", "Logs", - "History", + "Protocol", "Network", ]); }); diff --git a/clients/web/src/utils/inspectorTabs.ts b/clients/web/src/utils/inspectorTabs.ts index 0cc65de5..be69af73 100644 --- a/clients/web/src/utils/inspectorTabs.ts +++ b/clients/web/src/utils/inspectorTabs.ts @@ -13,7 +13,7 @@ export const INSPECTOR_TAB_IDS = [ "Resources", "Tasks", "Logs", - "History", + "Protocol", "Network", ] as const; diff --git a/clients/web/src/utils/oauthResume.test.ts b/clients/web/src/utils/oauthResume.test.ts index 43969179..c1fef0e9 100644 --- a/clients/web/src/utils/oauthResume.test.ts +++ b/clients/web/src/utils/oauthResume.test.ts @@ -20,7 +20,7 @@ import { EMPTY_APPS_UI, EMPTY_TASKS_UI, EMPTY_LOGS_UI, - EMPTY_HISTORY_UI, + EMPTY_PROTOCOL_UI, EMPTY_NETWORK_UI, } from "../components/screens/screenUiState.js"; @@ -106,7 +106,7 @@ describe("oauthResume", () => { appsUi: EMPTY_APPS_UI, tasksUi: EMPTY_TASKS_UI, logsUi: EMPTY_LOGS_UI, - historyUi: EMPTY_HISTORY_UI, + protocolUi: EMPTY_PROTOCOL_UI, networkUi: EMPTY_NETWORK_UI, }); const setToolsUi = vi.fn(); @@ -117,7 +117,7 @@ describe("oauthResume", () => { setAppsUi: vi.fn(), setTasksUi: vi.fn(), setLogsUi: vi.fn(), - setHistoryUi: vi.fn(), + setProtocolUi: vi.fn(), setNetworkUi: vi.fn(), }); expect(setToolsUi).toHaveBeenCalledWith(toolsUi); @@ -209,7 +209,7 @@ describe("oauthResume", () => { setAppsUi: vi.fn(), setTasksUi: vi.fn(), setLogsUi: vi.fn(), - setHistoryUi: vi.fn(), + setProtocolUi: vi.fn(), setNetworkUi: vi.fn(), setActiveTab, clearToolCallState, @@ -399,7 +399,7 @@ describe("oauthResume", () => { setAppsUi: vi.fn(), setTasksUi: vi.fn(), setLogsUi: vi.fn(), - setHistoryUi: vi.fn(), + setProtocolUi: vi.fn(), setNetworkUi: vi.fn(), }); expect(setToolsUi).not.toHaveBeenCalled(); @@ -413,7 +413,7 @@ describe("oauthResume", () => { setAppsUi: vi.fn(), setTasksUi: vi.fn(), setLogsUi: vi.fn(), - setHistoryUi: vi.fn(), + setProtocolUi: vi.fn(), setNetworkUi: vi.fn(), }; restoreTabUiFromSnapshot( @@ -433,7 +433,7 @@ describe("oauthResume", () => { setAppsUi: vi.fn(), setTasksUi: vi.fn(), setLogsUi: vi.fn(), - setHistoryUi: vi.fn(), + setProtocolUi: vi.fn(), setNetworkUi: vi.fn(), }; restoreTabUiFromSnapshot( @@ -444,7 +444,7 @@ describe("oauthResume", () => { Apps: EMPTY_APPS_UI, Tasks: EMPTY_TASKS_UI, Logs: EMPTY_LOGS_UI, - History: EMPTY_HISTORY_UI, + Protocol: EMPTY_PROTOCOL_UI, Network: EMPTY_NETWORK_UI, }, setters, @@ -455,7 +455,7 @@ describe("oauthResume", () => { expect(setters.setAppsUi).toHaveBeenCalledWith(EMPTY_APPS_UI); expect(setters.setTasksUi).toHaveBeenCalledWith(EMPTY_TASKS_UI); expect(setters.setLogsUi).toHaveBeenCalledWith(EMPTY_LOGS_UI); - expect(setters.setHistoryUi).toHaveBeenCalledWith(EMPTY_HISTORY_UI); + expect(setters.setProtocolUi).toHaveBeenCalledWith(EMPTY_PROTOCOL_UI); expect(setters.setNetworkUi).toHaveBeenCalledWith(EMPTY_NETWORK_UI); }); @@ -467,7 +467,7 @@ describe("oauthResume", () => { setAppsUi: vi.fn(), setTasksUi: vi.fn(), setLogsUi: vi.fn(), - setHistoryUi: vi.fn(), + setProtocolUi: vi.fn(), setNetworkUi: vi.fn(), }; restoreTabUiFromSnapshot( @@ -478,7 +478,7 @@ describe("oauthResume", () => { Apps: undefined, Tasks: undefined, Logs: undefined, - History: undefined, + Protocol: undefined, Network: undefined, }, setters, @@ -489,7 +489,7 @@ describe("oauthResume", () => { expect(setters.setAppsUi).toHaveBeenCalledWith(EMPTY_APPS_UI); expect(setters.setTasksUi).toHaveBeenCalledWith(EMPTY_TASKS_UI); expect(setters.setLogsUi).toHaveBeenCalledWith(EMPTY_LOGS_UI); - expect(setters.setHistoryUi).toHaveBeenCalledWith(EMPTY_HISTORY_UI); + expect(setters.setProtocolUi).toHaveBeenCalledWith(EMPTY_PROTOCOL_UI); expect(setters.setNetworkUi).toHaveBeenCalledWith(EMPTY_NETWORK_UI); }); }); diff --git a/clients/web/src/utils/oauthResume.ts b/clients/web/src/utils/oauthResume.ts index 81167ffa..4646e7df 100644 --- a/clients/web/src/utils/oauthResume.ts +++ b/clients/web/src/utils/oauthResume.ts @@ -6,7 +6,7 @@ import { EMPTY_APPS_UI, - EMPTY_HISTORY_UI, + EMPTY_PROTOCOL_UI, EMPTY_LOGS_UI, EMPTY_NETWORK_UI, EMPTY_PROMPTS_UI, @@ -15,7 +15,7 @@ import { EMPTY_TOOLS_UI, } from "../components/screens/screenUiState.js"; import type { AppsUiState } from "../components/screens/AppsScreen/AppsScreen.js"; -import type { HistoryUiState } from "../components/screens/HistoryScreen/HistoryScreen.js"; +import type { ProtocolUiState } from "../components/screens/ProtocolScreen/ProtocolScreen.js"; import type { LogsUiState } from "../components/screens/LoggingScreen/LoggingScreen.js"; import type { NetworkUiState } from "../components/screens/NetworkScreen/NetworkScreen.js"; import type { PromptsUiState } from "../components/screens/PromptsScreen/PromptsScreen.js"; @@ -65,7 +65,7 @@ export interface LiftedTabUiState { appsUi: AppsUiState; tasksUi: TasksUiState; logsUi: LogsUiState; - historyUi: HistoryUiState; + protocolUi: ProtocolUiState; networkUi: NetworkUiState; } @@ -76,7 +76,7 @@ export interface TabUiSetters { setAppsUi: (next: AppsUiState) => void; setTasksUi: (next: TasksUiState) => void; setLogsUi: (next: LogsUiState) => void; - setHistoryUi: (next: HistoryUiState) => void; + setProtocolUi: (next: ProtocolUiState) => void; setNetworkUi: (next: NetworkUiState) => void; } @@ -90,7 +90,7 @@ export function buildTabUiSnapshot( Resources: state.resourcesUi, Tasks: state.tasksUi, Logs: state.logsUi, - History: state.historyUi, + Protocol: state.protocolUi, Network: state.networkUi, }; } @@ -134,9 +134,9 @@ export function restoreTabUiFromSnapshot( case "Logs": setters.setLogsUi((value as LogsUiState | undefined) ?? EMPTY_LOGS_UI); break; - case "History": - setters.setHistoryUi( - (value as HistoryUiState | undefined) ?? EMPTY_HISTORY_UI, + case "Protocol": + setters.setProtocolUi( + (value as ProtocolUiState | undefined) ?? EMPTY_PROTOCOL_UI, ); break; case "Network":