From 8d434e23811a2ca6194d93eeecea5135a293513c Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sun, 22 Mar 2026 16:03:56 -0400 Subject: [PATCH] fix HistoryEntry children prop conflict and overlay CopyButton in ContentViewer Rename HistoryEntry's children prop to childEntries to avoid React's reserved prop name. Replace ContentViewer's onCopy callback with a copyable flag and overlay CopyButton in the top-right corner. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ContentViewer/ContentViewer.stories.tsx | 12 +++++-- .../atoms/ContentViewer/ContentViewer.tsx | 33 +++++++++++++------ .../HistoryEntry/HistoryEntry.stories.tsx | 2 +- .../molecules/HistoryEntry/HistoryEntry.tsx | 8 ++--- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/clients/web/src/components/atoms/ContentViewer/ContentViewer.stories.tsx b/clients/web/src/components/atoms/ContentViewer/ContentViewer.stories.tsx index a61e4256..5f7a72e9 100644 --- a/clients/web/src/components/atoms/ContentViewer/ContentViewer.stories.tsx +++ b/clients/web/src/components/atoms/ContentViewer/ContentViewer.stories.tsx @@ -1,5 +1,4 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; -import { fn } from "storybook/test"; import { ContentViewer } from "./ContentViewer"; const meta: Meta = { @@ -74,6 +73,15 @@ export const WithCopyButton: Story = { args: { type: "text", content: "Hello, world!\nThis text can be copied.", - onCopy: fn(), + copyable: true, + }, +}; + +export const JsonWithCopy: Story = { + args: { + type: "json", + content: + '{"name":"my-app","version":"1.0.0","description":"Sample configuration file"}', + copyable: true, }, }; diff --git a/clients/web/src/components/atoms/ContentViewer/ContentViewer.tsx b/clients/web/src/components/atoms/ContentViewer/ContentViewer.tsx index 90897d32..ef56384e 100644 --- a/clients/web/src/components/atoms/ContentViewer/ContentViewer.tsx +++ b/clients/web/src/components/atoms/ContentViewer/ContentViewer.tsx @@ -1,10 +1,11 @@ -import { Button, Code, Image, Stack } from "@mantine/core"; +import { Code, Flex, Image, Stack } from "@mantine/core"; +import { CopyButton } from "../CopyButton/CopyButton"; export interface ContentViewerProps { type: "text" | "json" | "image" | "audio"; content: string; mimeType?: string; - onCopy?: () => void; + copyable?: boolean; } function formatJson(content: string): string { @@ -15,16 +16,33 @@ function formatJson(content: string): string { } } +const ContentWrapper = Flex.withProps({ + pos: "relative", + direction: "column", +}); + export function ContentViewer({ type, content, mimeType, - onCopy, + copyable = false, }: ContentViewerProps) { + const showCopy = copyable && (type === "text" || type === "json"); + return ( - {type === "json" && {formatJson(content)}} - {type === "text" && {content}} + {(type === "json" || type === "text") && ( + + + {type === "json" ? formatJson(content) : content} + + {showCopy && ( + + + + )} + + )} {type === "image" && ( )} - {onCopy && ( - - )} ); } diff --git a/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.stories.tsx b/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.stories.tsx index ad2b4257..2fdc6394 100644 --- a/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.stories.tsx +++ b/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.stories.tsx @@ -75,7 +75,7 @@ export const WithChildren: Story = { response: { result: "completed", }, - children: [ + childEntries: [ { timestamp: "+120ms", method: "sampling/createMessage", diff --git a/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.tsx b/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.tsx index 600056e1..634b640b 100644 --- a/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.tsx +++ b/clients/web/src/components/molecules/HistoryEntry/HistoryEntry.tsx @@ -25,7 +25,7 @@ export interface HistoryEntryProps { durationMs: number; parameters?: Record; response?: Record; - children?: HistoryChildEntry[]; + childEntries?: HistoryChildEntry[]; isPinned: boolean; pinLabel?: string; isExpanded: boolean; @@ -42,7 +42,7 @@ export function HistoryEntry({ durationMs, parameters, response, - children, + childEntries, isPinned, pinLabel, isExpanded, @@ -102,9 +102,9 @@ export function HistoryEntry({ {JSON.stringify(response, null, 2)} )} - {children && children.length > 0 && ( + {childEntries && childEntries.length > 0 && ( - {children.map((child, index) => ( + {childEntries.map((child, index) => ( +--{" "}