From 40e0dab23f4c82a254578e486f5b578dd6b196c8 Mon Sep 17 00:00:00 2001 From: Anna Effort Date: Wed, 19 Aug 2026 00:10:17 -0700 Subject: [PATCH] Align Virtual Servers layout with the other components pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Grid becomes `grid-cols-1 / lg:2 / 2xl:3`. `col-span-full` and `sortServersForLayout` both go — cards are uniform now, so empty servers no longer need sorting to the end to keep a full-width row from splitting the grid. Servers render in API order. - Page shell moves from `space-y-9` to a `mb-6` heading, and the loading branch stops early-returning, so the title survives the spinner. - Cards drop `min-h-29` / `min-h-35` and their `justify-*` pairs. - New `--server-icon-bg` token joins `--tool-icon-bg` and `--prompt-icon-bg`; the card status dot stops hardcoding emerald/red and picks up `--tool-status-*`. - `ConnectSourceCard` adopts `AddToolsCard`'s add-icon token, hover and description leading. 2896 tests pass; tsc, eslint, prettier and build are clean. The e2e suite was not run — no local Chromium. Signed-off-by: Anna Effort --- e2e/virtual-servers.spec.ts | 31 +++++---- src/components/gateways/ConnectSourceCard.tsx | 8 +-- src/components/gateways/GatewayCards.test.tsx | 8 +-- .../gateways/VirtualServerCard.test.tsx | 13 ++-- src/components/gateways/VirtualServerCard.tsx | 28 ++------ src/index.css | 5 ++ src/pages/Gateways.test.tsx | 18 ++--- src/pages/Gateways.tsx | 69 ++++++++----------- 8 files changed, 82 insertions(+), 98 deletions(-) diff --git a/e2e/virtual-servers.spec.ts b/e2e/virtual-servers.spec.ts index 9fb903a5..e794b03d 100644 --- a/e2e/virtual-servers.spec.ts +++ b/e2e/virtual-servers.spec.ts @@ -367,7 +367,7 @@ test.describe("Virtual Servers page", () => { await expect(page.getByText("testVS")).toBeVisible(); const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" }); - await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-emerald-500/); + await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-active/); await expect(card.getByLabel("Enabled")).toBeVisible(); await expect(card.getByTestId("tool-count")).toHaveText("2"); @@ -435,7 +435,7 @@ test.describe("Virtual Servers page", () => { await page.waitForLoadState("networkidle"); const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" }); - await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-red-500/); + await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-inactive/); await expect(card.getByLabel("Disabled")).toBeVisible(); await page.getByRole("button", { name: "Actions for testVS" }).click(); @@ -443,7 +443,7 @@ test.describe("Virtual Servers page", () => { await expect.poll(() => stateRequestCount).toBe(1); await expect(page.getByRole("dialog")).toHaveCount(0); - await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-emerald-500/); + await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-active/); await expect(card.getByLabel("Enabled")).toBeVisible(); await expect( page.locator("[data-sonner-toast]").filter({ hasText: "testVS activated." }), @@ -485,7 +485,7 @@ test.describe("Virtual Servers page", () => { await dialog.getByRole("button", { name: "Cancel" }).click(); await expect(dialog).toHaveCount(0); expect(stateRequestCount).toBe(0); - await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-emerald-500/); + await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-active/); await page.getByRole("button", { name: "Actions for testVS" }).click(); await page.getByRole("menuitem", { name: "Deactivate" }).click(); @@ -496,7 +496,7 @@ test.describe("Virtual Servers page", () => { await expect.poll(() => stateRequestCount).toBe(1); await expect(dialog).toHaveCount(0); - await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-red-500/); + await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-inactive/); await expect(card.getByLabel("Disabled")).toBeVisible(); await expect( page.locator("[data-sonner-toast]").filter({ hasText: "testVS deactivated." }), @@ -1116,7 +1116,7 @@ test.describe("Virtual Servers page", () => { ); }); - test("disables the Upload action button on virtual server cards", async ({ page }) => { + test("shows only the actions menu in the virtual server card header", async ({ page }) => { await page.route("**/servers?*", async (route) => { await route.fulfill({ status: 200, @@ -1128,7 +1128,8 @@ test.describe("Virtual Servers page", () => { await page.goto(APP.GATEWAYS); await page.waitForLoadState("networkidle"); - await expect(page.getByRole("button", { name: /Open testVS \(coming soon\)/ })).toBeDisabled(); + await expect(page.getByRole("button", { name: /Open testVS \(coming soon\)/ })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Actions for testVS" })).toBeVisible(); }); test("navigates to create server UI from the create server card", async ({ page }) => { @@ -1183,7 +1184,7 @@ test.describe("Virtual Servers page", () => { const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" }); - await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-red-500/); + await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-inactive/); await expect(card.getByRole("img", { name: "Disabled" })).toBeVisible(); await expect(card.getByText("disabled")).toBeVisible(); @@ -1220,7 +1221,7 @@ test.describe("Virtual Servers page", () => { await expect(card2.getByText("private")).toBeVisible(); }); - test("places empty virtual servers after servers with components", async ({ page }) => { + test("renders virtual servers in the order the API returned them", async ({ page }) => { const emptyServer = { ...MOCK_VIRTUAL_SERVER, id: "empty-server-id", @@ -1244,13 +1245,15 @@ test.describe("Virtual Servers page", () => { await page.goto(APP.GATEWAYS); await page.waitForLoadState("networkidle"); + // Cards are uniform now, so empty servers are no longer sorted to the end + // to keep a full-width row from splitting the grid. const cards = page.getByTestId("virtual-server-card"); await expect(cards).toHaveCount(2); - await expect(cards.nth(0)).toHaveAttribute("data-server-name", "testVS"); - await expect(cards.nth(1)).toHaveAttribute("data-server-name", "peach-thistle-shark"); - await expect(cards.nth(1)).toHaveClass(/col-span-full/); + await expect(cards.nth(0)).toHaveAttribute("data-server-name", "peach-thistle-shark"); + await expect(cards.nth(1)).toHaveAttribute("data-server-name", "testVS"); + await expect(cards.nth(0)).not.toHaveClass(/col-span-full/); await expect( - cards.nth(1).getByRole("button", { name: "Add sources and components" }), + cards.nth(0).getByRole("button", { name: "Add sources and components" }), ).toBeVisible(); }); @@ -1322,7 +1325,7 @@ test.describe("Virtual Servers page", () => { const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" }); - await expect(card).toHaveClass(/col-span-full/); + await expect(card).not.toHaveClass(/col-span-full/); await expect(card.getByRole("button", { name: "Add sources and components" })).toBeVisible(); await expect(card.getByTestId("tool-count")).toHaveCount(0); await expect(card.getByTestId("resource-count")).toHaveCount(0); diff --git a/src/components/gateways/ConnectSourceCard.tsx b/src/components/gateways/ConnectSourceCard.tsx index 73f3ee97..a95fdfb0 100644 --- a/src/components/gateways/ConnectSourceCard.tsx +++ b/src/components/gateways/ConnectSourceCard.tsx @@ -10,7 +10,7 @@ export function ConnectSourceCard({ onAction }: { onAction: () => void }) { size="sm" role="button" tabIndex={0} - className="min-h-35 cursor-pointer justify-center transition-colors hover:bg-muted/40" + className="cursor-pointer transition-opacity hover:opacity-90" onClick={onAction} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { @@ -21,14 +21,14 @@ export function ConnectSourceCard({ onAction }: { onAction: () => void }) { >
- - + + {intl.formatMessage({ id: "gateways.createServer.card.title" })}
- + {intl.formatMessage({ id: "gateways.createServer.card.description" })} diff --git a/src/components/gateways/GatewayCards.test.tsx b/src/components/gateways/GatewayCards.test.tsx index 0d6b25fc..a09de877 100644 --- a/src/components/gateways/GatewayCards.test.tsx +++ b/src/components/gateways/GatewayCards.test.tsx @@ -92,14 +92,14 @@ describe("VirtualServerCard", () => { expect(screen.getByText("My Test Server")).toBeTruthy(); }); - it("shows green status indicator for enabled server", () => { + it("shows an active status indicator for enabled server", () => { renderWithProviders(); - expect(screen.getByTestId("status-indicator")).toHaveClass("bg-emerald-500"); + expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-active"); }); - it("shows red status indicator for disabled server", () => { + it("shows an inactive status indicator for disabled server", () => { renderWithProviders(); - expect(screen.getByTestId("status-indicator")).toHaveClass("bg-red-500"); + expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-inactive"); }); it("calls onViewDetails when card is clicked", () => { diff --git a/src/components/gateways/VirtualServerCard.test.tsx b/src/components/gateways/VirtualServerCard.test.tsx index 000368dc..64192094 100644 --- a/src/components/gateways/VirtualServerCard.test.tsx +++ b/src/components/gateways/VirtualServerCard.test.tsx @@ -77,19 +77,19 @@ describe("VirtualServerCard", () => { expect(onViewDetails).toHaveBeenCalledWith(expect.objectContaining({ id: "vs-1" })); }); - it("shows a green enabled indicator for enabled server", () => { + it("shows an active enabled indicator for enabled server", () => { renderWithProviders( , ); - expect(screen.getByTestId("status-indicator")).toHaveClass("bg-emerald-500"); + expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-active"); expect(screen.getByRole("img", { name: "Enabled" })).toBeTruthy(); }); - it("shows a red disabled indicator for disabled server", () => { + it("shows an inactive disabled indicator for disabled server", () => { renderWithProviders( , ); - expect(screen.getByTestId("status-indicator")).toHaveClass("bg-red-500"); + expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-inactive"); expect(screen.getByRole("img", { name: "Disabled" })).toBeTruthy(); }); @@ -231,13 +231,14 @@ describe("VirtualServerCard", () => { expect(document.querySelector(".custom-class")).toBeTruthy(); }); - it("shows upload button for non-empty server", () => { + it("shows only the actions menu in the card header", () => { renderWithProviders( , ); - expect(screen.getByRole("button", { name: /Open.*coming soon/i })).toBeTruthy(); + expect(screen.queryByRole("button", { name: /Open.*coming soon/i })).toBeNull(); + expect(screen.getByRole("button", { name: /Actions for/i })).toBeTruthy(); }); }); diff --git a/src/components/gateways/VirtualServerCard.tsx b/src/components/gateways/VirtualServerCard.tsx index d101a487..e3bb9374 100644 --- a/src/components/gateways/VirtualServerCard.tsx +++ b/src/components/gateways/VirtualServerCard.tsx @@ -16,7 +16,7 @@ import { } from "@/components/ui/dropdown-menu"; import { cn } from "@/lib/utils"; import type { VirtualServer } from "@/types/server"; -import { Box, EllipsisVertical, MessageSquareCode, Plus, Upload, Wrench } from "lucide-react"; +import { Box, EllipsisVertical, MessageSquareCode, Plus, Wrench } from "lucide-react"; import { useIntl } from "react-intl"; export function VirtualServerCard({ @@ -52,26 +52,22 @@ export function VirtualServerCard({ return ( onViewDetails(server)} >
- - + +
- {server.name} + {server.name}
- {!isEmptyComposition && ( - - )}