inspector/clients/web/src/components/screens/ProtocolScreen/ProtocolScreen.tsx
Cliff Hall 8d33e76f97
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
web: server-screen, footer, monitoring-sidebar, and responsive-header polish (#1683)
Closes #1682

Server-screen, footer, monitoring-sidebar, and responsive-header polish: connected-card highlight/scroll + Failed status, footer row + MCP-docs tooltip, Servers box, card-surface refresh (gray-0 entry cards + white code), monitoring-sidebar layout, a 1280px responsive floor honored by the header (always-icon Disconnect, always-SegmentedControl tabs, 1500px status text, always-available sidebar), a horizontally-scrollable long resource URI in the compact Protocol entry, and copy buttons beside resource URIs / network URLs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-14 08:55:39 -04:00

149 lines
4.5 KiB
TypeScript

import { useCallback, useMemo } from "react";
import { Card, Flex, Stack } from "@mantine/core";
import type {
MessageEntry,
MessageMethod,
MessageOrigin,
} from "@inspector/core/mcp/types.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 ProtocolScreenProps {
entries: MessageEntry[];
pinnedIds: Set<string>;
ui: ProtocolUiState;
onUiChange: (next: ProtocolUiState) => void;
onClearAll: () => void;
onExport: () => void;
onClearSection: (section: "pinned" | "history") => void;
onExportSection: (section: "pinned" | "history") => void;
onReplay: (id: string) => void;
onTogglePin: (id: string) => void;
sortDirection: SortDirection;
onSortChange: (next: SortDirection) => void;
compact: boolean;
onToggleCompact: () => void;
/** See LoggingScreen: fills the parent height and drops the filter sidebar. */
embedded?: boolean;
}
// 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 ProtocolUiState {
search: string;
methodFilter?: MessageMethod;
/** Which message directions are shown, keyed by entry origin. */
visibleDirections: Record<MessageOrigin, boolean>;
}
const ScreenLayout = Flex.withProps({
variant: "screen",
h: "calc(100dvh - var(--app-shell-header-height, 0px) - var(--app-shell-footer-height, 0px))",
gap: "md",
p: "xl",
});
const Sidebar = Stack.withProps({
w: 340,
flex: "0 0 auto",
});
const SidebarCard = Card.withProps({
withBorder: true,
padding: "lg",
});
export function ProtocolScreen({
entries,
pinnedIds,
ui,
onUiChange,
onClearAll,
onExport,
onClearSection,
onExportSection,
onReplay,
onTogglePin,
sortDirection,
onSortChange,
compact,
onToggleCompact,
embedded = false,
}: ProtocolScreenProps) {
const { search, methodFilter, visibleDirections } = ui;
const availableMethods = useMemo(
() => Array.from(new Set(entries.map(extractMethod))).sort(),
[entries],
);
const handleClearAll = useCallback(() => {
onUiChange({ ...ui, methodFilter: undefined });
onClearAll();
}, [ui, onUiChange, onClearAll]);
const handleToggleDirection = useCallback(
(direction: MessageOrigin, visible: boolean) => {
onUiChange({
...ui,
visibleDirections: { ...visibleDirections, [direction]: visible },
});
},
[ui, visibleDirections, onUiChange],
);
const handleToggleAllDirections = useCallback(() => {
const next = !Object.values(visibleDirections).every(Boolean);
onUiChange({
...ui,
visibleDirections: { client: next, server: next },
});
}, [ui, visibleDirections, onUiChange]);
return (
// See LoggingScreen: only override `h` when embedded, so the standalone
// screen keeps ScreenLayout's default full-screen height (a `h={undefined}`
// would clobber it and collapse an empty screen to its controls' height).
<ScreenLayout {...(embedded ? { h: "100%", pt: "md" } : {})}>
{embedded ? null : (
<Sidebar>
<SidebarCard>
<ProtocolControls
searchText={search}
methodFilter={methodFilter}
availableMethods={availableMethods}
visibleDirections={visibleDirections}
onSearchChange={(value) => onUiChange({ ...ui, search: value })}
onMethodFilterChange={(value) =>
onUiChange({ ...ui, methodFilter: value })
}
onToggleDirection={handleToggleDirection}
onToggleAllDirections={handleToggleAllDirections}
/>
</SidebarCard>
</Sidebar>
)}
<ProtocolListPanel
entries={entries}
pinnedIds={pinnedIds}
searchText={search}
methodFilter={methodFilter}
visibleDirections={visibleDirections}
onClearAll={handleClearAll}
onExport={onExport}
onClearSection={onClearSection}
onExportSection={onExportSection}
onReplay={onReplay}
onTogglePin={onTogglePin}
sortDirection={sortDirection}
onSortChange={onSortChange}
compact={compact}
onToggleCompact={onToggleCompact}
embedded={embedded}
/>
</ScreenLayout>
);
}