diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx index 5e005100..48295bcf 100644 --- a/clients/web/src/App.tsx +++ b/clients/web/src/App.tsx @@ -102,6 +102,7 @@ import { useInspectorVersion } from "@inspector/core/react/useInspectorVersion.j import { usePendingClientRequests } from "@inspector/core/react/usePendingClientRequests.js"; import { InspectorView } from "./components/views/InspectorView/InspectorView"; import { VersionBadge } from "./components/elements/VersionBadge/VersionBadge"; +import { CopyrightBadge } from "./components/elements/CopyrightBadge/CopyrightBadge"; import type { ToolCallState, ToolsUiState, @@ -3824,6 +3825,7 @@ function App() { onRefreshApps={onRefreshTools} /> + = { + title: "Elements/CopyrightBadge", + component: CopyrightBadge, +}; + +export default meta; +type Story = StoryObj; + +// The grey copyright notice pinned to the lower-left corner of the viewport. +export const Default: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByText(COPYRIGHT_NOTICE)).toBeInTheDocument(); + }, +}; diff --git a/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.test.tsx b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.test.tsx new file mode 100644 index 00000000..271d5ba8 --- /dev/null +++ b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.test.tsx @@ -0,0 +1,17 @@ +import { describe, it, expect } from "vitest"; +import { renderWithMantine, screen } from "../../../test/renderWithMantine"; +import { CopyrightBadge, COPYRIGHT_NOTICE } from "./CopyrightBadge"; + +describe("CopyrightBadge", () => { + it("renders the project copyright notice", () => { + renderWithMantine(); + expect(screen.getByText(COPYRIGHT_NOTICE)).toBeInTheDocument(); + }); + + it("names the Model Context Protocol and LF Projects", () => { + renderWithMantine(); + expect( + screen.getByText(/Model Context Protocol.*Series of LF Projects, LLC\./), + ).toBeInTheDocument(); + }); +}); diff --git a/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.tsx b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.tsx new file mode 100644 index 00000000..49f1fb7c --- /dev/null +++ b/clients/web/src/components/elements/CopyrightBadge/CopyrightBadge.tsx @@ -0,0 +1,18 @@ +import { Text } from "@mantine/core"; + +/** The project's copyright notice. */ +export const COPYRIGHT_NOTICE = + "Copyright © Model Context Protocol a Series of LF Projects, LLC."; + +// The `copyrightBadge` variant (src/theme/Text.ts) pins it to the lower-right +// corner in grey, on the same row as the version badge, and non-interactive. +const CopyrightText = Text.withProps({ variant: "copyrightBadge" }); + +/** + * The project copyright notice, fixed to the lower-right corner of the screen + * (#1639) — the grey, non-interactive twin of the lower-left version badge, + * sharing its row. + */ +export function CopyrightBadge() { + return {COPYRIGHT_NOTICE}; +} diff --git a/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx b/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx index db7468f5..fbb834d7 100644 --- a/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx +++ b/clients/web/src/components/elements/VersionBadge/VersionBadge.tsx @@ -5,12 +5,12 @@ export interface VersionBadgeProps { version?: string; } -// The `versionBadge` variant (src/theme/Text.ts) pins it to the lower-right +// The `versionBadge` variant (src/theme/Text.ts) pins it to the lower-left // corner in grey and makes it non-interactive. const VersionText = Text.withProps({ variant: "versionBadge" }); /** - * A small, unobtrusive build-version label fixed to the lower-right corner of + * A small, unobtrusive build-version label fixed to the lower-left corner of * the screen (#1639). Sourced from the root `package.json` via the backend's * `GET /api/config`; renders nothing until the version is known (or on a legacy * backend that omits it). diff --git a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx index 28c8fbcf..97e9f3ee 100644 --- a/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx +++ b/clients/web/src/components/groups/MonitoringControls/MonitoringControls.tsx @@ -1,4 +1,4 @@ -import { ActionIcon, Group, SegmentedControl, TextInput } from "@mantine/core"; +import { Button, Group, SegmentedControl, TextInput } from "@mantine/core"; import { TbLayoutSidebarRightCollapse } from "react-icons/tb"; import { ClearButton } from "../../elements/ClearButton/ClearButton"; @@ -69,15 +69,14 @@ export function MonitoringControls({ ) : null } /> - - + ); } diff --git a/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx b/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx index d2d2e5ab..3cfde766 100644 --- a/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx +++ b/clients/web/src/components/groups/ServerListControls/ServerListControls.test.tsx @@ -39,8 +39,9 @@ describe("ServerListControls", () => { onToggleList={onToggleList} />, ); - const buttons = screen.getAllByRole("button"); - await user.click(buttons[0]); + await user.click( + screen.getByRole("button", { name: /Expand all|Collapse all/ }), + ); expect(onToggleList).toHaveBeenCalledTimes(1); }); diff --git a/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx b/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx index a76c2f3c..ed6f0351 100644 --- a/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx +++ b/clients/web/src/components/groups/ServerListControls/ServerListControls.tsx @@ -27,9 +27,6 @@ export function ServerListControls({ }: ServerListControlsProps) { return ( - {serverCount > 0 && ( - - )} @@ -40,6 +37,9 @@ export function ServerListControls({ onImportServerJson={onImportServerJson} /> )} + {serverCount > 0 && ( + + )} ); } diff --git a/clients/web/src/theme/Text.ts b/clients/web/src/theme/Text.ts index 1a635108..7a9ae782 100644 --- a/clients/web/src/theme/Text.ts +++ b/clients/web/src/theme/Text.ts @@ -37,15 +37,29 @@ export const ThemeText = Text.extend({ }, }; } - // A small, unobtrusive build-version label pinned to the lower-right corner - // of the screen (#1639): grey, non-interactive (clicks pass through), and - // out of the tab/selection flow so it never interferes with the UI beneath. - if (props.variant === "versionBadge") { + // Two small, unobtrusive labels pinned to the bottom corners of the screen + // on the same row (#1639): the build version (bottom-left) and the + // copyright notice (bottom-right). Each occupies the full-height `xl` bottom + // margin band (`height: xl` + flex centering) so the text sits vertically + // centered in it, and insets by `xl` horizontally to line up with the + // content's left/right margins. Both are grey, non-interactive (clicks pass + // through), and out of the tab/selection flow. + if ( + props.variant === "versionBadge" || + props.variant === "copyrightBadge" + ) { + const horizontal = + props.variant === "versionBadge" + ? { left: "var(--mantine-spacing-xl)" } + : { right: "var(--mantine-spacing-xl)" }; return { root: { position: "fixed", - bottom: "0.35rem", - right: "0.6rem", + bottom: 0, + height: "var(--mantine-spacing-xl)", + display: "flex", + alignItems: "center", + ...horizontal, zIndex: 100, color: "var(--inspector-text-secondary)", fontSize: "var(--mantine-font-size-xs)",