diff --git a/clients/web/src/components/groups/HistoryControls/HistoryControls.stories.tsx b/clients/web/src/components/groups/HistoryControls/HistoryControls.stories.tsx index 0e7910d8..d2cdad7f 100644 --- a/clients/web/src/components/groups/HistoryControls/HistoryControls.stories.tsx +++ b/clients/web/src/components/groups/HistoryControls/HistoryControls.stories.tsx @@ -5,12 +5,6 @@ import { HistoryControls } from "./HistoryControls"; const meta: Meta = { title: "Groups/HistoryControls", component: HistoryControls, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { args: { searchText: "", onSearchChange: fn(), @@ -18,11 +12,14 @@ export const Default: Story = { }, }; +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + export const WithFilters: Story = { args: { searchText: "tools", methodFilter: "tools/call", - onSearchChange: fn(), - onMethodFilterChange: fn(), }, }; diff --git a/clients/web/src/components/groups/HistoryControls/HistoryControls.tsx b/clients/web/src/components/groups/HistoryControls/HistoryControls.tsx index 086e6526..b5756c9d 100644 --- a/clients/web/src/components/groups/HistoryControls/HistoryControls.tsx +++ b/clients/web/src/components/groups/HistoryControls/HistoryControls.tsx @@ -15,7 +15,7 @@ export interface HistoryControlsProps { searchText: string; methodFilter?: string; onSearchChange: (text: string) => void; - onMethodFilterChange: (method: string) => void; + onMethodFilterChange: (method: string | undefined) => void; } export function HistoryControls({ @@ -38,7 +38,7 @@ export function HistoryControls({ placeholder="All methods" data={METHOD_OPTIONS} value={methodFilter ?? null} - onChange={(value) => onMethodFilterChange(value ?? "")} + onChange={(value) => onMethodFilterChange(value ?? undefined)} clearable /> diff --git a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.stories.tsx b/clients/web/src/components/groups/HistoryEntry/HistoryEntry.stories.tsx index 54dd7631..637b4c77 100644 --- a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.stories.tsx +++ b/clients/web/src/components/groups/HistoryEntry/HistoryEntry.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { fn } from "storybook/test"; import { HistoryEntry } from "./HistoryEntry"; @@ -14,13 +15,79 @@ const meta: Meta = { export default meta; type Story = StoryObj; +const toolCallEntry: MessageEntry = { + id: "req-1", + timestamp: new Date("2026-03-17T10:30:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { name: "get_weather", arguments: { city: "San Francisco" } }, + }, + response: { + jsonrpc: "2.0", + id: 1, + result: { + content: [{ type: "text", text: "18°C, partly cloudy" }], + }, + }, + duration: 142, +}; + +const errorEntry: MessageEntry = { + id: "req-2", + timestamp: new Date("2026-03-17T10:31:15Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 2, + method: "tools/call", + params: { name: "query_database" }, + }, + response: { + jsonrpc: "2.0", + id: 2, + error: { code: -32000, message: "Connection timeout" }, + }, + duration: 3200, +}; + +const resourceReadEntry: MessageEntry = { + id: "req-3", + timestamp: new Date("2026-03-17T10:33:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 3, + method: "resources/read", + params: { uri: "file:///config.json" }, + }, + response: { + jsonrpc: "2.0", + id: 3, + result: { + contents: [{ uri: "file:///config.json", text: '{"debug": true}' }], + }, + }, + duration: 45, +}; + +const pendingEntry: MessageEntry = { + id: "req-4", + timestamp: new Date("2026-03-17T10:34:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 4, + method: "tools/call", + params: { name: "long_operation" }, + }, +}; + export const SuccessCollapsed: Story = { args: { - timestamp: "2026-03-17T10:30:00Z", - method: "tools/call", - target: "get_weather", - status: "success", - durationMs: 142, + entry: toolCallEntry, isPinned: false, isListExpanded: false, }, @@ -28,79 +95,32 @@ export const SuccessCollapsed: Story = { export const SuccessExpanded: Story = { args: { - timestamp: "2026-03-17T10:30:00Z", - method: "tools/call", - target: "get_weather", - status: "success", - durationMs: 142, + entry: toolCallEntry, isPinned: false, isListExpanded: true, - parameters: { - city: "San Francisco", - units: "celsius", - }, - response: { - temperature: 18, - conditions: "Partly cloudy", - humidity: 65, - }, }, }; export const Error: Story = { args: { - timestamp: "2026-03-17T10:31:15Z", - method: "tools/call", - target: "query_database", - status: "error", - durationMs: 3200, - isPinned: false, - isListExpanded: false, - }, -}; - -export const WithChildren: Story = { - args: { - timestamp: "2026-03-17T10:32:00Z", - method: "tools/call", - target: "complex_operation", - status: "success", - durationMs: 1250, + entry: errorEntry, isPinned: false, isListExpanded: true, - parameters: { - action: "process", - }, - response: { - result: "completed", - }, - childEntries: [ - { - timestamp: "+120ms", - method: "sampling/createMessage", - target: "gpt-4", - status: "success", - durationMs: 800, - }, - { - timestamp: "+950ms", - method: "elicitation/create", - target: "confirm_action", - status: "success", - durationMs: 200, - }, - ], }, }; export const Pinned: Story = { args: { - timestamp: "2026-03-17T10:33:00Z", - method: "resources/read", - target: "config.json", - status: "success", - durationMs: 45, + entry: resourceReadEntry, isPinned: true, isListExpanded: false, }, }; + +export const Pending: Story = { + args: { + entry: pendingEntry, + isPinned: false, + isListExpanded: false, + }, +}; diff --git a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx b/clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx index 76246ebf..0d795f94 100644 --- a/clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx +++ b/clients/web/src/components/groups/HistoryEntry/HistoryEntry.tsx @@ -9,25 +9,12 @@ import { Stack, Text, } from "@mantine/core"; +import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { ContentViewer } from "../../elements/ContentViewer/ContentViewer"; - -export interface HistoryChildEntry { - timestamp: string; - method: string; - target?: string; - status: "success" | "error"; - durationMs: number; -} +import { extractMethod } from "../historyUtils.js"; export interface HistoryEntryProps { - timestamp: string; - method: string; - target?: string; - status: "success" | "error"; - durationMs: number; - parameters?: Record; - response?: Record; - childEntries?: HistoryChildEntry[]; + entry: MessageEntry; isPinned: boolean; isListExpanded: boolean; onReplay: () => void; @@ -65,64 +52,79 @@ const SubtleButton = Button.withProps({ size: "xs", }); -const ChildMethodBadge = Badge.withProps({ - color: "dark", - size: "sm", -}); - function formatDuration(ms: number): string { return `${ms}ms`; } -function formatStatusLabel(status: "success" | "error"): string { - return status === "success" ? "OK" : "Error"; -} - -function statusColor(status: "success" | "error"): string { - return status === "success" ? "green" : "red"; +function formatTimestamp(date: Date): string { + return date.toISOString(); } function formatPinLabel(isPinned: boolean): string { return isPinned ? "Unpin" : "Pin"; } -function serializeJson(value: Record): string { +function extractTarget(entry: MessageEntry): string | undefined { + const msg = entry.message; + if (!("params" in msg) || !msg.params) return undefined; + const params = msg.params as Record; + if (typeof params.name === "string") return params.name; + if (typeof params.uri === "string") return params.uri; + return undefined; +} + +function extractStatus(entry: MessageEntry): "success" | "error" | "pending" { + if (!entry.response) return "pending"; + if ("error" in entry.response) return "error"; + return "success"; +} + +function statusColor(status: "success" | "error" | "pending"): string { + if (status === "success") return "green"; + if (status === "error") return "red"; + return "gray"; +} + +function statusLabel(status: "success" | "error" | "pending"): string { + if (status === "success") return "OK"; + if (status === "error") return "Error"; + return "Pending"; +} + +function serializeMessage(value: unknown): string { return JSON.stringify(value); } export function HistoryEntry({ - timestamp, - method, - target, - status, - durationMs, - parameters, - response, - childEntries, + entry, isPinned, isListExpanded, onReplay, onTogglePin, }: HistoryEntryProps) { const [isExpanded, setIsExpanded] = useState(isListExpanded); + const method = extractMethod(entry); + const target = extractTarget(entry); + const status = extractStatus(entry); useEffect(() => { setIsExpanded(isListExpanded); }, [isListExpanded]); + return ( - {timestamp} + {formatTimestamp(entry.timestamp)} {method} {target && {target}} - {formatDuration(durationMs)} - - {formatStatusLabel(status)} - + {entry.duration != null && ( + {formatDuration(entry.duration)} + )} + {statusLabel(status)} @@ -136,57 +138,35 @@ export function HistoryEntry({ - {isExpanded && ( - - - {parameters && ( - <> - - - Parameters: - - - - )} - {response && ( - - Response: - - - )} - {childEntries && childEntries.length > 0 && ( - - {childEntries.map((child, index) => ( - - +-- - {child.timestamp} - {child.method} - {child.target && {child.target}} - - {formatStatusLabel(child.status)} - - - {formatDuration(child.durationMs)} - - - ))} - - )} - - - )} + + + + {"params" in entry.message && entry.message.params && ( + + Parameters: + + + )} + {entry.response && ( + + Response: + + + )} + + ); diff --git a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx b/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx index 1acfcddb..7516e22c 100644 --- a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx +++ b/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.stories.tsx @@ -1,72 +1,105 @@ 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 type { HistoryEntryProps } from "../HistoryEntry/HistoryEntry"; const meta: Meta = { title: "Groups/HistoryListPanel", component: HistoryListPanel, args: { searchText: "", + pinnedIds: new Set(), onClearAll: fn(), onExport: fn(), + onReplay: fn(), + onTogglePin: fn(), }, }; export default meta; type Story = StoryObj; -function makeEntry(overrides: Partial): HistoryEntryProps { - return { - timestamp: "2026-03-17T10:00:00Z", - method: "tools/call", - target: "send_message", - status: "success", - durationMs: 120, - isPinned: false, - isListExpanded: false, - onReplay: fn(), - onTogglePin: fn(), - ...overrides, - }; -} +const sampleEntries: MessageEntry[] = [ + { + id: "req-1", + timestamp: new Date("2026-03-17T10:00:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { name: "send_message", arguments: { message: "Hello!" } }, + }, + response: { + jsonrpc: "2.0", + id: 1, + result: { content: [{ type: "text", text: "Sent" }] }, + }, + duration: 120, + }, + { + id: "req-2", + timestamp: new Date("2026-03-17T10:01:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 2, + method: "resources/read", + params: { uri: "file:///config.json" }, + }, + response: { + jsonrpc: "2.0", + id: 2, + result: { + contents: [{ uri: "file:///config.json", text: "{}" }], + }, + }, + duration: 45, + }, + { + id: "req-3", + timestamp: new Date("2026-03-17T10:02:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { name: "delete_records", arguments: { ids: [1, 2, 3] } }, + }, + response: { + jsonrpc: "2.0", + id: 3, + error: { code: -32000, message: "Permission denied" }, + }, + duration: 350, + }, + { + id: "req-4", + timestamp: new Date("2026-03-17T09:30:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 4, + method: "tools/list", + }, + response: { + jsonrpc: "2.0", + id: 4, + result: { tools: [] }, + }, + duration: 80, + }, +]; export const WithEntries: Story = { args: { - entries: [ - makeEntry({ - timestamp: "2026-03-17T10:00:00Z", - method: "tools/call", - target: "send_message", - }), - makeEntry({ - timestamp: "2026-03-17T10:01:00Z", - method: "resources/read", - target: "config.json", - durationMs: 45, - }), - makeEntry({ - timestamp: "2026-03-17T10:02:00Z", - method: "tools/call", - target: "delete_records", - status: "error", - durationMs: 350, - }), - ], - pinnedEntries: [ - makeEntry({ - timestamp: "2026-03-17T09:30:00Z", - method: "tools/list", - isPinned: true, - durationMs: 80, - }), - ], + entries: sampleEntries, + pinnedIds: new Set(["req-4"]), }, }; export const Empty: Story = { args: { entries: [], - pinnedEntries: [], }, }; diff --git a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.tsx b/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.tsx index 0940a35d..dbda18f1 100644 --- a/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.tsx +++ b/clients/web/src/components/groups/HistoryListPanel/HistoryListPanel.tsx @@ -8,17 +8,20 @@ import { Text, Title, } from "@mantine/core"; +import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { HistoryEntry } from "../HistoryEntry/HistoryEntry"; import { ListToggle } from "../../elements/ListToggle/ListToggle"; -import type { HistoryEntryProps } from "../HistoryEntry/HistoryEntry"; +import { extractMethod } from "../historyUtils.js"; -export interface HistoryRequestsPanelProps { - entries: HistoryEntryProps[]; - pinnedEntries: HistoryEntryProps[]; +export interface HistoryListPanelProps { + entries: MessageEntry[]; + pinnedIds: Set; searchText: string; methodFilter?: string; onClearAll: () => void; onExport: () => void; + onReplay: (id: string) => void; + onTogglePin: (id: string) => void; } const ToolbarButton = Button.withProps({ @@ -39,10 +42,6 @@ const EmptyState = Text.withProps({ py: "xl", }); -function entryKey(entry: HistoryEntryProps): string { - return `${entry.timestamp}-${entry.method}`; -} - function formatPinnedTitle(count: number): string { return `Pinned Requests (${count})`; } @@ -52,16 +51,17 @@ function formatHistoryTitle(count: number): string { } function matchesFilters( - entry: HistoryEntryProps, + entry: MessageEntry, searchText: string, methodFilter?: string, ): boolean { - if (methodFilter && entry.method !== methodFilter) return false; + const method = extractMethod(entry); + if (methodFilter && method !== methodFilter) return false; if (searchText) { const term = searchText.toLowerCase(); const responseText = entry.response ? JSON.stringify(entry.response) : ""; const searchable = - `${entry.method} ${entry.target ?? ""} ${entry.timestamp} ${responseText}`.toLowerCase(); + `${method} ${entry.id} ${JSON.stringify(entry.message)} ${responseText}`.toLowerCase(); if (!searchable.includes(term)) return false; } return true; @@ -69,12 +69,14 @@ function matchesFilters( export function HistoryListPanel({ entries, - pinnedEntries, + pinnedIds, searchText, methodFilter, onClearAll, onExport, -}: HistoryRequestsPanelProps) { + onReplay, + onTogglePin, +}: HistoryListPanelProps) { const [compact, setCompact] = useState(false); const filteredEntries = useMemo( @@ -82,13 +84,17 @@ export function HistoryListPanel({ [entries, searchText, methodFilter], ); - const filteredPinned = useMemo( - () => - pinnedEntries.filter((e) => matchesFilters(e, searchText, methodFilter)), - [pinnedEntries, searchText, methodFilter], + const pinnedEntries = useMemo( + () => filteredEntries.filter((e) => pinnedIds.has(e.id)), + [filteredEntries, pinnedIds], ); - const hasResults = filteredEntries.length > 0 || filteredPinned.length > 0; + const unpinnedEntries = useMemo( + () => filteredEntries.filter((e) => !pinnedIds.has(e.id)), + [filteredEntries, pinnedIds], + ); + + const hasResults = filteredEntries.length > 0; return ( @@ -110,34 +116,40 @@ export function HistoryListPanel({ ) : ( - {filteredPinned.length > 0 && ( + {pinnedEntries.length > 0 && ( <> - {formatPinnedTitle(filteredPinned.length)} + {formatPinnedTitle(pinnedEntries.length)} - {filteredPinned.map((entry) => ( + {pinnedEntries.map((entry) => ( onReplay(entry.id)} + onTogglePin={() => onTogglePin(entry.id)} /> ))} )} - {filteredEntries.length > 0 && ( + {unpinnedEntries.length > 0 && ( <> - {formatHistoryTitle(filteredEntries.length)} + {formatHistoryTitle(unpinnedEntries.length)} Clear - {filteredEntries.map((entry) => ( + {unpinnedEntries.map((entry) => ( onReplay(entry.id)} + onTogglePin={() => onTogglePin(entry.id)} /> ))} diff --git a/clients/web/src/components/groups/historyUtils.ts b/clients/web/src/components/groups/historyUtils.ts new file mode 100644 index 00000000..6180feb8 --- /dev/null +++ b/clients/web/src/components/groups/historyUtils.ts @@ -0,0 +1,8 @@ +import type { MessageEntry } from "../../../../../core/mcp/types.js"; + +export function extractMethod(entry: MessageEntry): string { + if ("method" in entry.message) { + return entry.message.method; + } + return "response"; +} diff --git a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx b/clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx index 0d66d218..592ffbad 100644 --- a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx +++ b/clients/web/src/components/screens/HistoryScreen/HistoryScreen.stories.tsx @@ -1,101 +1,126 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { fn } from "storybook/test"; import { HistoryScreen } from "./HistoryScreen"; -import type { HistoryEntryProps } from "../../groups/HistoryEntry/HistoryEntry"; const meta: Meta = { title: "Screens/HistoryScreen", component: HistoryScreen, parameters: { layout: "fullscreen" }, args: { + pinnedIds: new Set(), onClearAll: fn(), onExport: fn(), + onReplay: fn(), + onTogglePin: fn(), }, }; export default meta; type Story = StoryObj; -const entry1: HistoryEntryProps = { - timestamp: "2026-03-17T10:00:00Z", - method: "tools/call", - target: "send_message", - status: "success", - durationMs: 120, - parameters: { message: "Hello, world!" }, - response: { result: "Message sent successfully" }, - isPinned: false, - isListExpanded: false, - onReplay: fn(), - onTogglePin: fn(), -}; - -const entry2: HistoryEntryProps = { - timestamp: "2026-03-17T10:01:00Z", - method: "resources/read", - target: "config.json", - status: "success", - durationMs: 45, - parameters: { uri: "file:///config.json" }, - response: { contents: [{ uri: "file:///config.json", text: "{}" }] }, - isPinned: false, - isListExpanded: false, - onReplay: fn(), - onTogglePin: fn(), -}; - -const entry3: HistoryEntryProps = { - timestamp: "2026-03-17T10:02:00Z", - method: "tools/call", - target: "delete_records", - status: "error", - durationMs: 350, - parameters: { ids: [1, 2, 3] }, - response: { error: "Permission denied" }, - isPinned: false, - isListExpanded: true, - onReplay: fn(), - onTogglePin: fn(), -}; - -const pinnedEntry1: HistoryEntryProps = { - timestamp: "2026-03-17T09:30:00Z", - method: "tools/list", - status: "success", - durationMs: 80, - response: { tools: ["send_message", "list_users"] }, - isPinned: true, - isListExpanded: false, - onReplay: fn(), - onTogglePin: fn(), -}; - -const pinnedEntry2: HistoryEntryProps = { - timestamp: "2026-03-17T09:35:00Z", - method: "prompts/get", - target: "greeting", - status: "success", - durationMs: 60, - parameters: { name: "greeting" }, - response: { - messages: [{ role: "user", content: { type: "text", text: "Hello!" } }], +const sampleEntries: MessageEntry[] = [ + { + id: "req-1", + timestamp: new Date("2026-03-17T10:00:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { name: "send_message", arguments: { message: "Hello, world!" } }, + }, + response: { + jsonrpc: "2.0", + id: 1, + result: { + content: [{ type: "text", text: "Message sent successfully" }], + }, + }, + duration: 120, }, - isPinned: true, - isListExpanded: false, - onReplay: fn(), - onTogglePin: fn(), -}; + { + id: "req-2", + timestamp: new Date("2026-03-17T10:01:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 2, + method: "resources/read", + params: { uri: "file:///config.json" }, + }, + response: { + jsonrpc: "2.0", + id: 2, + result: { + contents: [{ uri: "file:///config.json", text: "{}" }], + }, + }, + duration: 45, + }, + { + id: "req-3", + timestamp: new Date("2026-03-17T10:02:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 3, + method: "tools/call", + params: { name: "delete_records", arguments: { ids: [1, 2, 3] } }, + }, + response: { + jsonrpc: "2.0", + id: 3, + error: { code: -32000, message: "Permission denied" }, + }, + duration: 350, + }, + { + id: "req-4", + timestamp: new Date("2026-03-17T09:30:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 4, + method: "tools/list", + }, + response: { + jsonrpc: "2.0", + id: 4, + result: { tools: [] }, + }, + duration: 80, + }, + { + id: "req-5", + timestamp: new Date("2026-03-17T09:35:00Z"), + direction: "request", + message: { + jsonrpc: "2.0", + id: 5, + method: "prompts/get", + params: { name: "greeting" }, + }, + response: { + jsonrpc: "2.0", + id: 5, + result: { + messages: [{ role: "user", content: { type: "text", text: "Hello!" } }], + }, + }, + duration: 60, + }, +]; export const WithEntries: Story = { args: { - entries: [entry1, entry2, entry3], - pinnedEntries: [pinnedEntry1, pinnedEntry2], + entries: sampleEntries, + pinnedIds: new Set(["req-4", "req-5"]), }, }; export const Empty: Story = { args: { entries: [], - pinnedEntries: [], }, }; diff --git a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx b/clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx index bcb3513d..0917322c 100644 --- a/clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx +++ b/clients/web/src/components/screens/HistoryScreen/HistoryScreen.tsx @@ -1,14 +1,16 @@ import { useState } from "react"; import { Card, Flex, Stack } from "@mantine/core"; +import type { MessageEntry } from "../../../../../../core/mcp/types.js"; import { HistoryControls } from "../../groups/HistoryControls/HistoryControls"; import { HistoryListPanel } from "../../groups/HistoryListPanel/HistoryListPanel.js"; -import type { HistoryEntryProps } from "../../groups/HistoryEntry/HistoryEntry"; export interface HistoryScreenProps { - entries: HistoryEntryProps[]; - pinnedEntries: HistoryEntryProps[]; + entries: MessageEntry[]; + pinnedIds: Set; onClearAll: () => void; onExport: () => void; + onReplay: (id: string) => void; + onTogglePin: (id: string) => void; } const ScreenLayout = Flex.withProps({ @@ -30,9 +32,11 @@ const SidebarCard = Card.withProps({ export function HistoryScreen({ entries, - pinnedEntries, + pinnedIds, onClearAll, onExport, + onReplay, + onTogglePin, }: HistoryScreenProps) { const [searchText, setSearchText] = useState(""); const [methodFilter, setMethodFilter] = useState(); @@ -45,19 +49,19 @@ export function HistoryScreen({ searchText={searchText} methodFilter={methodFilter} onSearchChange={setSearchText} - onMethodFilterChange={(value) => - setMethodFilter(value || undefined) - } + onMethodFilterChange={setMethodFilter} /> ); diff --git a/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx b/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx index e2832878..02c28771 100644 --- a/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx +++ b/clients/web/src/components/views/ConnectedView/ConnectedView.stories.tsx @@ -599,79 +599,98 @@ export const HistoryActive: Story = { ), },