Merge pull request #1634 from modelcontextprotocol/1623-rename-history-to-protocol

refactor(web): rename "History" tab/feature to "Protocol" (#1623)
This commit is contained in:
Cliff Hall 2026-07-09 21:49:01 -04:00 • committed by GitHub
commit 50e780ff71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 524 additions and 429 deletions

View file

@ -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<string>;
onClearProtocol: () => void;
onReplayProtocol: (id: string) => void;
onTogglePinProtocol: (id: string) => void;
pinnedProtocolIds?: Set<string>;
}) => (
<div>
<span data-testid="tool-status">
@ -419,15 +419,15 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({
<button onClick={() => props.onSetLogLevel("debug")}>set-level</button>
<button onClick={() => props.onServerSettings("A")}>open-settings</button>
<span data-testid="pinned-history">
{Array.from(props.pinnedHistoryIds ?? []).join(",")}
{Array.from(props.pinnedProtocolIds ?? []).join(",")}
</span>
<button onClick={() => props.onTogglePinHistory("hist-1")}>
<button onClick={() => props.onTogglePinProtocol("hist-1")}>
toggle-pin
</button>
<button onClick={() => props.onReplayHistory("hist-1")}>
<button onClick={() => props.onReplayProtocol("hist-1")}>
replay-history
</button>
<button onClick={() => props.onClearHistory()}>clear-history</button>
<button onClick={() => props.onClearProtocol()}>clear-history</button>
</div>
),
}));

View file

@ -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<string, unknown> | undefined,
tools: Tool[],
): Promise<string | null> {
// 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<Set<string>>(
const [pinnedProtocolIds, setPinnedProtocolIds] = useState<Set<string>>(
() => 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<string, unknown> | 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}

View file

@ -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<HTMLDivElement>();
renderWithMantine(
<EmbeddableScrollArea
embedded={embedded}
viewportRef={ref}
constrainContentWidth
>
<div>constrained content</div>
</EmbeddableScrollArea>,
);
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");
},
);
});

View file

@ -17,10 +17,25 @@ export interface EmbeddableScrollAreaProps {
embedded: boolean;
viewportRef: Ref<HTMLDivElement>;
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 (
<Stack flex={1} mih={0} gap={0}>
@ -37,6 +54,7 @@ export function EmbeddableScrollArea({
mah="100%"
type="scroll"
offsetScrollbars
styles={styles}
>
{children}
</ScrollArea.Autosize>
@ -49,6 +67,7 @@ export function EmbeddableScrollArea({
mah={FULLSIZE_MAH}
type="scroll"
offsetScrollbars
styles={styles}
>
{children}
</ScrollArea.Autosize>

View file

@ -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

View file

@ -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

View file

@ -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();
},
};

View file

@ -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(<MessageDirectionBadge direction="incoming" />);
expect(screen.getByText("client ← server")).toBeInTheDocument();
expect(screen.getByText("server → client")).toBeInTheDocument();
});
});

View file

@ -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<MessageDirectionBadgeProps["direction"], string> = {
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,

View file

@ -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

View file

@ -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.
*/

View file

@ -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);
});

View file

@ -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({

View file

@ -6,7 +6,7 @@ const meta: Meta<typeof MonitoringControls> = {
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();

View file

@ -6,7 +6,7 @@ import {
type MonitoringControlsProps,
} from "./MonitoringControls";
const TABS = ["Logs", "History", "Network"];
const TABS = ["Logs", "Protocol", "Network"];
function renderControls(overrides: Partial<MonitoringControlsProps> = {}) {
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 () => {

View file

@ -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;

View file

@ -9,7 +9,7 @@ const demoScreens = {
<Text>Log stream</Text>
</Box>
),
History: (
Protocol: (
<Box p="md">
<Text>Request history</Text>
</Box>
@ -32,7 +32,7 @@ const meta: Meta<typeof MonitoringScreen> = {
),
],
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();

View file

@ -6,12 +6,12 @@ import {
type MonitoringScreenProps,
} from "./MonitoringScreen";
const TABS = ["Logs", "History", "Network"];
const TABS = ["Logs", "Protocol", "Network"];
function screens() {
return {
Logs: <div>logs-body</div>,
History: <div>history-body</div>,
Protocol: <div>history-body</div>,
Network: <div>network-body</div>,
};
}
@ -33,7 +33,7 @@ function renderScreen(overrides: Partial<MonitoringScreenProps> = {}) {
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();

View file

@ -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;

View file

@ -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(() => {

Some files were not shown because too many files have changed in this diff Show more