fix(web): remove mantine-border-animate, use green highlight border (#1536)

* fix(web): remove mantine-border-animate, use green highlight border (#1535)

The @gfazioli/mantine-border-animate dependency emitted a persistent
React "forwardRef render functions accept exactly two parameters"
warning on every page load. Remove the dependency entirely and replace
the freshly-added card's animated border with a green border.

- Drop @gfazioli/mantine-border-animate from clients/web (package + lock)
  and remove its styles.css import from main.tsx.
- Add a `highlighted` variant to the Card theme that draws a 2px green
  border, driven by a new --inspector-highlight-border token in App.css.
- ServerCard now selects the variant (disabled > highlighted) instead of
  wrapping in BorderAnimate; the BorderAnimate flex-wrapper `w="100%"`
  workaround is no longer needed.
- Update ServerCard/ServerListScreen tests to assert the highlighted
  variant instead of the removed BorderAnimate DOM.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(web): drop redundant Card props on ServerCard (#1535)

ThemeCard.defaultProps already sets withBorder and padding="lg", so the
explicit props on ServerCard's <Card> were redundant. Addresses review
feedback on PR #1536.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(web): note highlighted Card variant relies on inherited withBorder (#1535)

Addresses a maintainability flag from the PR #1536 re-review: the
highlighted variant only sets borderColor/borderWidth and depends on the
withBorder:true default, so make that coupling explicit in a comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-06-27 17:04:25 -04:00 • committed by GitHub
commit 0b0bd40e5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 48 additions and 75 deletions

View file

@ -50,6 +50,9 @@
/* Red glow pulsed around a newly appearing tab's label (#1450) */
--inspector-glow-red: var(--mantine-color-red-6);
/* Green border drawn around a freshly-added/highlighted server card (#1535) */
--inspector-highlight-border: var(--mantine-color-green-6);
/* ── Log levels (RFC 5424) ─────────────────────────────── */
--inspector-log-debug: var(--mantine-color-gray-6);
--inspector-log-info: var(--mantine-color-blue-6);

View file

@ -291,6 +291,22 @@ describe("ServerCard", () => {
}
});
it("draws the green highlight-variant border when highlighted", () => {
const { container } = renderWithMantine(
<ServerCard {...baseProps} highlighted />,
);
expect(
container.querySelector('[data-variant="highlighted"]'),
).not.toBeNull();
});
it("does not draw the highlight border when not highlighted", () => {
const { container } = renderWithMantine(<ServerCard {...baseProps} />);
expect(
container.querySelector('[data-variant="highlighted"]'),
).toBeNull();
});
it("does not scroll when not highlighted", () => {
const scrollIntoView = vi.fn();
const orig = Element.prototype.scrollIntoView;

View file

@ -1,6 +1,5 @@
import { useEffect, useRef, type ReactNode } from "react";
import { Badge, Button, Card, Group, Stack, Text } from "@mantine/core";
import { BorderAnimate } from "@gfazioli/mantine-border-animate";
import type {
MCPServerConfig,
ServerEntry,
@ -35,9 +34,8 @@ export interface ServerCardProps extends ServerEntry {
*/
dragHandle?: ReactNode;
/**
* When true, the card is freshly added: it draws an animated border (Mantine
* Border Animate) to draw the eye. Cleared by `onClearHighlight` on any click
* on the card.
* When true, the card is freshly added: it draws a green border to draw the
* eye. Cleared by `onClearHighlight` on any click on the card.
*/
highlighted?: boolean;
/**
@ -47,7 +45,7 @@ export interface ServerCardProps extends ServerEntry {
* Defaults to true.
*/
scrollOnHighlight?: boolean;
/** Called when a highlighted card is clicked, to dismiss the animated border. */
/** Called when a highlighted card is clicked, to dismiss the green border. */
onClearHighlight?: () => void;
}
@ -156,16 +154,18 @@ export function ServerCard({
const protocolVersion =
connection.status === "connected" ? connection.protocolVersion : undefined;
const card = (
// A dimmed card (another server is active) is inert, so the disabled variant
// wins; otherwise a freshly-added card draws the highlighted green border.
const variant = isDimmed
? "disabled"
: highlighted
? "highlighted"
: undefined;
return (
<Card
ref={rootRef}
withBorder
padding="lg"
// BorderAnimate's wrapper is display:flex, so the card must stretch to
// fill it — otherwise the card shrinks to content width while the
// animated border spans the full grid cell.
w="100%"
variant={isDimmed ? "disabled" : undefined}
variant={variant}
onClick={highlighted ? onClearHighlight : undefined}
{...(isDimmed ? { "aria-disabled": true, inert: true } : {})}
>
@ -237,19 +237,4 @@ export function ServerCard({
</Stack>
</Card>
);
// Always render the wrapper and toggle `show`/`animate` rather than
// conditionally wrapping — swapping the element type on clear would remount
// the card (and its connect toggle / buttons), swallowing the click that
// triggered the clear.
return (
<BorderAnimate
show={highlighted}
animate={highlighted}
radius="md"
w="100%"
>
{card}
</BorderAnimate>
);
}

View file

@ -137,16 +137,16 @@ describe("ServerListScreen", () => {
});
describe("freshly-added highlight", () => {
it("draws an animated border on every highlighted server", () => {
it("draws a green highlight border on every highlighted server", () => {
const { container } = renderWithMantine(
<ServerListScreen
{...baseProps}
highlightedServerIds={["alpha", "beta"]}
/>,
);
// One animated border per highlighted card.
// One highlighted-variant card per highlighted server.
expect(
container.querySelectorAll(".mantine-BorderAnimate-border"),
container.querySelectorAll('[data-variant="highlighted"]'),
).toHaveLength(2);
});

View file

@ -4,7 +4,6 @@ import { MantineProvider, type CSSVariablesResolver } from "@mantine/core";
import { Notifications } from "@mantine/notifications";
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import "@gfazioli/mantine-border-animate/styles.css";
import "./App.css";
import { theme } from "./theme/theme";
import App from "./App";

View file

@ -37,6 +37,19 @@ export const ThemeCard = Card.extend({
},
};
}
if (props.variant === "highlighted") {
// Freshly-added / called-out server card: a prominent green border draws
// the eye until the highlight is dismissed (#1535). Only the color/width
// are set here — the border itself comes from the inherited
// `withBorder: true` default above, so keep that default if changing it.
return {
root: {
backgroundColor: "var(--inspector-surface-card)",
borderColor: "var(--inspector-highlight-border)",
borderWidth: 2,
},
};
}
if (props.variant === "preview") {
// Container for the resource preview / template form panels: sizes to
// content (no forced height) but caps at the screen's available area