web: server-screen, footer, monitoring-sidebar, and responsive-header polish (#1683)
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

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)
This commit is contained in:
Cliff Hall 2026-07-14 08:55:39 -04:00 • committed by GitHub
commit 8d33e76f97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 707 additions and 274 deletions

View file

@ -4,14 +4,14 @@ import { Text } from "@mantine/core";
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.
// The `copyrightBadge` variant (src/theme/Text.ts) styles it as a small grey
// single-line label; the footer row positions it at the right (#1682).
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.
* The project copyright notice, shown at the right of the footer row (#1682,
* superseding the fixed lower-right badge of #1639) — the grey twin of the
* left-aligned version badge, sharing the footer.
*/
export function CopyrightBadge() {
return <CopyrightText>{COPYRIGHT_NOTICE}</CopyrightText>;

View file

@ -4,7 +4,7 @@ import { ScrollArea, Stack } from "@mantine/core";
// Full-size, the monitor stream panels bound their scroll to the viewport
// minus the header and the panel's own chrome.
const FULLSIZE_MAH =
"calc(100vh - var(--app-shell-header-height, 0px) - 150px)";
"calc(100vh - var(--app-shell-header-height, 0px) - var(--app-shell-footer-height, 0px) - 150px)";
export interface EmbeddableScrollAreaProps {
/**

View file

@ -28,6 +28,17 @@ export const Disconnected: Story = {
},
};
// A failed connection settles the status back to "disconnected", but the
// `failed` flag surfaces it as a red "Failed" instead of the grey
// "Disconnected" (#1682).
export const Failed: Story = {
args: {
status: "disconnected",
failed: true,
showLabel: true,
},
};
export const Error: Story = {
args: {
status: "error",

View file

@ -39,6 +39,27 @@ describe("ServerStatusIndicator", () => {
expect(screen.getByText("Error")).toBeInTheDocument();
});
it("renders 'Failed' with the error color when failed, overriding the disconnected status (#1682)", () => {
const { container } = renderWithMantine(
<ServerStatusIndicator status="disconnected" failed showLabel />,
);
expect(screen.getByText("Failed")).toBeInTheDocument();
expect(screen.queryByText("Disconnected")).not.toBeInTheDocument();
// The dot uses the error color, not the disconnected grey.
const dot = container.querySelector(".mantine-Paper-root");
expect(dot?.getAttribute("style") ?? "").toContain(
"inspector-status-error",
);
});
it("does not show 'Failed' when the failed flag is unset (a deliberate disconnect)", () => {
renderWithMantine(
<ServerStatusIndicator status="disconnected" showLabel />,
);
expect(screen.getByText("Disconnected")).toBeInTheDocument();
expect(screen.queryByText("Failed")).not.toBeInTheDocument();
});
it("hides label and uses title when showLabel is false", () => {
renderWithMantine(
<ServerStatusIndicator

View file

@ -6,6 +6,13 @@ export interface ServerStatusIndicatorProps {
status: ConnectionStatus;
latencyMs?: number;
retryCount?: number;
/**
* True when the server's last connection attempt failed (#1682). The
* indicator then shows a red dot + "Failed" instead of the settled
* grey "Disconnected", so a failed connect reads distinctly from a
* deliberate disconnect. Overrides `status` for the label and color.
*/
failed?: boolean;
// Override for the default viewport-based label visibility. Useful when
// the indicator renders inside a constrained container (sidebar, card)
// where the global media query doesn't reflect available space.
@ -46,16 +53,30 @@ export function ServerStatusIndicator({
status,
latencyMs,
retryCount,
failed = false,
showLabel: showLabelProp,
}: ServerStatusIndicatorProps) {
const wideViewport = useMediaQuery("(min-width: 1200px)");
// Drop the text label below 1500px. "Connected (Nms)" is the header's widest
// optional element, so shedding it first — earlier than the Disconnect control,
// which collapses at the 1280px floor (see ViewHeader) — keeps the tab row from
// crowding. Below the breakpoint the indicator is a dot only, with the label
// moved to its `title` tooltip.
const wideViewport = useMediaQuery("(min-width: 1500px)");
const showLabel = showLabelProp ?? wideViewport;
const label = getLabel(status, latencyMs, retryCount);
// A failed connection settles the status back to "disconnected"; the `failed`
// flag distinguishes it as a failure (red + "Failed") from a deliberate
// disconnect (grey), overriding the status-derived label and color.
const label = failed ? "Failed" : getLabel(status, latencyMs, retryCount);
const color = failed
? "var(--inspector-status-error)"
: statusColorVar[status];
return (
<Group gap="xs">
<Dot
bg={statusColorVar[status]}
className={status === "connecting" ? "inspector-pulse" : undefined}
bg={color}
className={
!failed && status === "connecting" ? "inspector-pulse" : undefined
}
title={showLabel ? undefined : label}
/>
{showLabel && <Text size="sm">{label}</Text>}

View file

@ -5,15 +5,15 @@ export interface VersionBadgeProps {
version?: string;
}
// The `versionBadge` variant (src/theme/Text.ts) pins it to the lower-left
// corner in grey and makes it non-interactive.
// The `versionBadge` variant (src/theme/Text.ts) styles it as a small grey
// single-line label; the footer row positions it at the left (#1682).
const VersionText = Text.withProps({ variant: "versionBadge" });
/**
* 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).
* A small, unobtrusive build-version label shown at the left of the footer row
* (#1682, superseding the fixed lower-left badge of #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).
*/
export function VersionBadge({ version }: VersionBadgeProps) {
if (!version) return null;