diff --git a/clients/web/package-lock.json b/clients/web/package-lock.json
index e6de271a..a14dc7d5 100644
--- a/clients/web/package-lock.json
+++ b/clients/web/package-lock.json
@@ -12,7 +12,6 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.14.0",
- "@gfazioli/mantine-border-animate": "^2.0.3",
"@hono/node-server": "^1.19.14",
"@mantine/core": "^8.3.17",
"@mantine/form": "^8.3.17",
@@ -1242,19 +1241,6 @@
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
- "node_modules/@gfazioli/mantine-border-animate": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/@gfazioli/mantine-border-animate/-/mantine-border-animate-2.0.3.tgz",
- "integrity": "sha512-6JmO4Cox2KAjIXWWNIewwuudTtTGS5QrcWqjgch8Kn1Zu2bwhZAk4n1y2z7YNJsnfZGVM3XcxLbdGivJcCiNMQ==",
- "license": "MIT",
- "peerDependencies": {
- "@mantine/core": ">=7.0.0",
- "@mantine/hooks": ">=7.0.0",
- "@tabler/icons-react": "^3.34.0",
- "react": "^18.x || ^19.x",
- "react-dom": "^18.x || ^19.x"
- }
- },
"node_modules/@hono/node-server": {
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
@@ -2689,34 +2675,6 @@
}
}
},
- "node_modules/@tabler/icons": {
- "version": "3.44.0",
- "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.44.0.tgz",
- "integrity": "sha512-Wn0AOZG9sg0L+bjfMqq4eNhC6pQjIrk94LvvWYNYkY8KH8wC3YILRzQlrnVJc4FUeMxH/AK97QsYCX35H3LndA==",
- "license": "MIT",
- "peer": true,
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/codecalm"
- }
- },
- "node_modules/@tabler/icons-react": {
- "version": "3.44.0",
- "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.44.0.tgz",
- "integrity": "sha512-8+rvzBbVm/1Z3sG3x7GUNAaxIKxwgz8xaMhRs23nrCnMTKRFAhEC+82zAIFeAA0seXdrAGX5HFCkaLpGK2rVHg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@tabler/icons": "3.44.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/codecalm"
- },
- "peerDependencies": {
- "react": ">= 16"
- }
- },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
diff --git a/clients/web/package.json b/clients/web/package.json
index 791ad1c6..9c3e075d 100644
--- a/clients/web/package.json
+++ b/clients/web/package.json
@@ -36,7 +36,6 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@emotion/react": "^11.14.0",
- "@gfazioli/mantine-border-animate": "^2.0.3",
"@hono/node-server": "^1.19.14",
"@mantine/core": "^8.3.17",
"@mantine/form": "^8.3.17",
diff --git a/clients/web/src/App.css b/clients/web/src/App.css
index 9807a3bd..111d4015 100644
--- a/clients/web/src/App.css
+++ b/clients/web/src/App.css
@@ -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);
diff --git a/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx b/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx
index bc5b74e6..ccf7c2b5 100644
--- a/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx
+++ b/clients/web/src/components/groups/ServerCard/ServerCard.test.tsx
@@ -291,6 +291,22 @@ describe("ServerCard", () => {
}
});
+ it("draws the green highlight-variant border when highlighted", () => {
+ const { container } = renderWithMantine(
+ ,
+ );
+ expect(
+ container.querySelector('[data-variant="highlighted"]'),
+ ).not.toBeNull();
+ });
+
+ it("does not draw the highlight border when not highlighted", () => {
+ const { container } = renderWithMantine();
+ expect(
+ container.querySelector('[data-variant="highlighted"]'),
+ ).toBeNull();
+ });
+
it("does not scroll when not highlighted", () => {
const scrollIntoView = vi.fn();
const orig = Element.prototype.scrollIntoView;
diff --git a/clients/web/src/components/groups/ServerCard/ServerCard.tsx b/clients/web/src/components/groups/ServerCard/ServerCard.tsx
index 1c3c3482..a93a6c30 100644
--- a/clients/web/src/components/groups/ServerCard/ServerCard.tsx
+++ b/clients/web/src/components/groups/ServerCard/ServerCard.tsx
@@ -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 (
@@ -237,19 +237,4 @@ export function ServerCard({
);
-
- // 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 (
-
- {card}
-
- );
}
diff --git a/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx b/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx
index 19b7bf2a..7bf332ae 100644
--- a/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx
+++ b/clients/web/src/components/screens/ServerListScreen/ServerListScreen.test.tsx
@@ -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(
,
);
- // 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);
});
diff --git a/clients/web/src/main.tsx b/clients/web/src/main.tsx
index 486061ca..662352e8 100644
--- a/clients/web/src/main.tsx
+++ b/clients/web/src/main.tsx
@@ -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";
diff --git a/clients/web/src/theme/Card.ts b/clients/web/src/theme/Card.ts
index eaa5b586..1b597ec4 100644
--- a/clients/web/src/theme/Card.ts
+++ b/clients/web/src/theme/Card.ts
@@ -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