Refactor ServerCard with active server logic, rename StatusIndicator, and polish UI panels

- Rename StatusIndicator to ServerStatusIndicator across all files
- ServerCard: add activeServer/onSetActiveServer props for coordinated
  connection flow — when a card's toggle is switched on, it sets itself
  as the active server, shows "Connecting" status, and dims all other
  cards (opacity + pointerEvents none + inert for full keyboard blocking)
- ServerListScreen: manage activeServer state internally, pass to cards;
  switch to full-width layout with 3-column grid and scrollable card area
- ServerCard: move "Test Client Features" dropdown above ContentViewer
  with min-height row for consistent card heights
- ToolResultPanel: move Clear button to top-right header row
- ToolDetailPanel: right-align Execute Tool button, size md, not full-width
- ListChangedIndicator: bump Refresh button to size sm
- ToolsActive story: expand to 30 tools with titles
- UnconnectedView stories: all servers disconnected, remove Connecting story

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-04-02 18:00:18 -04:00
commit 8e73bc6ffb
4 changed files with 295 additions and 117 deletions

View file

@ -16,7 +16,7 @@ export function ConnectionToggle({
return (
<Switch
size="lg"
checked={checked}
checked={checked || loading}
disabled={disabled || loading}
onChange={(event) => onChange(event.currentTarget.checked)}
/>

View file

@ -18,7 +18,7 @@ const UpdateLabel = Text.withProps({
});
const RefreshButton = Button.withProps({
size: "xs",
size: "sm",
variant: "light",
});

View file

@ -0,0 +1,42 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ServerStatusIndicator } from "./ServerStatusIndicator";
const meta: Meta<typeof ServerStatusIndicator> = {
title: "Elements/ServerStatusIndicator",
component: ServerStatusIndicator,
};
export default meta;
type Story = StoryObj<typeof ServerStatusIndicator>;
export const Connected: Story = {
args: {
status: "connected",
latencyMs: 23,
},
};
export const Connecting: Story = {
args: {
status: "connecting",
},
};
export const Disconnected: Story = {
args: {
status: "disconnected",
},
};
export const Failed: Story = {
args: {
status: "failed",
},
};
export const FailedWithRetries: Story = {
args: {
status: "failed",
retryCount: 3,
},
};

View file

@ -0,0 +1,53 @@
import { Group, Paper, Text } from "@mantine/core";
export interface ServerStatusIndicatorProps {
status: "connected" | "connecting" | "disconnected" | "failed";
latencyMs?: number;
retryCount?: number;
}
const statusColorVar: Record<ServerStatusIndicatorProps["status"], string> = {
connected: "var(--inspector-status-connected)",
connecting: "var(--inspector-status-connecting)",
disconnected: "var(--inspector-status-disconnected)",
failed: "var(--inspector-status-failed)",
};
function getLabel(
status: ServerStatusIndicatorProps["status"],
latencyMs?: number,
retryCount?: number,
): string {
switch (status) {
case "connected":
return latencyMs != null ? `Connected (${latencyMs}ms)` : "Connected";
case "connecting":
return "Connecting...";
case "disconnected":
return "Disconnected";
case "failed":
return retryCount != null ? `Failed (${retryCount})` : "Failed";
}
}
const Dot = Paper.withProps({
w: 10,
h: 10,
radius: "xl",
});
export function ServerStatusIndicator({
status,
latencyMs,
retryCount,
}: ServerStatusIndicatorProps) {
return (
<Group gap="xs">
<Dot
bg={statusColorVar[status]}
className={status === "connecting" ? "inspector-pulse" : undefined}
/>
<Text size="sm">{getLabel(status, latencyMs, retryCount)}</Text>
</Group>
);
}