feat(web): red border on a server card whose connection attempt failed (#1621)
Flags the server whose last connection attempt failed with a red card border, cleared when another server is connected or a new connection is attempted — a persistent, at-a-glance companion to the failure toast and the auto-opened monitoring column. - App tracks `failedServerId`: set in onToggleConnection's non-auth handshake catch, cleared at the start of any new connect attempt. It is deliberately kept out of resetSessionScopedUiState, since the failure's own `disconnect` event would otherwise clear it the same tick (that same event clears `activeServer`, which is why the flag is tracked separately). - New `errored` Card theme variant (red border via a new `--inspector-error-border` token), mirroring the `highlighted` green border. Precedence: disabled (dimmed) > errored > highlighted. - Plumbed `erroredServerId` through InspectorView → ServerListScreen → ServerCard (`errored` prop). Full `npm run ci` passes (validate + coverage + smoke + storybook). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
This commit is contained in:
parent
46b311ad5c
commit
c6999dff96
3 changed files with 219 additions and 6 deletions
|
|
@ -87,6 +87,21 @@ export const Failed: Story = {
|
|||
},
|
||||
};
|
||||
|
||||
// The red border flagging a server whose last connection attempt failed
|
||||
// (#1621). The status settles back to "disconnected" (the failure clears the
|
||||
// active session), but the border persists until another server is
|
||||
// connected/attempted.
|
||||
export const ErroredBorder: Story = {
|
||||
args: {
|
||||
id: "3d2c1b0a-9f8e-4d6c-8b7a-1a2b3c4d5e6f",
|
||||
name: "My MCP Server",
|
||||
config: stdioConfig,
|
||||
info: { name: "My MCP Server", version: "1.2.0" },
|
||||
connection: disconnected,
|
||||
errored: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const LongServerName: Story = {
|
||||
args: {
|
||||
id: "a1b2c3d4-e5f6-4789-9abc-def012345678",
|
||||
|
|
|
|||
|
|
@ -363,4 +363,56 @@ describe("ServerCard", () => {
|
|||
expect(onClearHighlight).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("connection-failed border (#1621)", () => {
|
||||
it("draws the red errored-variant border when errored", () => {
|
||||
const { container } = renderWithMantine(
|
||||
<ServerCard {...baseProps} connection={disconnected} errored />,
|
||||
);
|
||||
expect(
|
||||
container.querySelector('[data-variant="errored"]'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not draw the errored border when not errored", () => {
|
||||
const { container } = renderWithMantine(
|
||||
<ServerCard {...baseProps} connection={disconnected} />,
|
||||
);
|
||||
expect(container.querySelector('[data-variant="errored"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("prefers the dimmed (disabled) variant over the errored border", () => {
|
||||
// A dimmed card (another server active) is inert; that wins over the
|
||||
// error border so the card can't look interactive.
|
||||
const { container } = renderWithMantine(
|
||||
<ServerCard
|
||||
{...baseProps}
|
||||
connection={disconnected}
|
||||
activeServer="other"
|
||||
errored
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
container.querySelector('[data-variant="disabled"]'),
|
||||
).not.toBeNull();
|
||||
expect(container.querySelector('[data-variant="errored"]')).toBeNull();
|
||||
});
|
||||
|
||||
it("prefers the errored border over the freshly-added highlight", () => {
|
||||
const { container } = renderWithMantine(
|
||||
<ServerCard
|
||||
{...baseProps}
|
||||
connection={disconnected}
|
||||
errored
|
||||
highlighted
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
container.querySelector('[data-variant="errored"]'),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
container.querySelector('[data-variant="highlighted"]'),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ export interface ServerCardProps extends ServerEntry {
|
|||
* eye. Cleared by `onClearHighlight` on any click on the card.
|
||||
*/
|
||||
highlighted?: boolean;
|
||||
/**
|
||||
* When true, this server's last connection attempt failed: the card draws a
|
||||
* red border to flag it (#1621). The parent clears this when another server
|
||||
* is connected or a new connection is attempted.
|
||||
*/
|
||||
errored?: boolean;
|
||||
/**
|
||||
* Whether a highlighted card scrolls itself into view. When several cards are
|
||||
* highlighted at once (a batch import) only the first should scroll, so the
|
||||
|
|
@ -135,6 +141,7 @@ export function ServerCard({
|
|||
writable = true,
|
||||
dragHandle,
|
||||
highlighted = false,
|
||||
errored = false,
|
||||
scrollOnHighlight = true,
|
||||
onClearHighlight,
|
||||
}: ServerCardProps) {
|
||||
|
|
@ -155,12 +162,15 @@ export function ServerCard({
|
|||
connection.status === "connected" ? connection.protocolVersion : undefined;
|
||||
|
||||
// A dimmed card (another server is active) is inert, so the disabled variant
|
||||
// wins; otherwise a freshly-added card draws the highlighted green border.
|
||||
// wins; otherwise a failed-connection card draws the red error border, then a
|
||||
// freshly-added card draws the highlighted green border.
|
||||
const variant = isDimmed
|
||||
? "disabled"
|
||||
: highlighted
|
||||
? "highlighted"
|
||||
: undefined;
|
||||
: errored
|
||||
? "errored"
|
||||
: highlighted
|
||||
? "highlighted"
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Card
|
||||
|
|
|
|||
Loading…
Reference in a new issue