feat(frontend): Topologies page — catalog browser + builder
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

New authed workspace page (/topologies, in the global nav) that browses the
topology catalog and builds + visualizes a topology from a kind + roles:
- lib/api/topology.ts: zod schemas + fetchTopologyCatalog (server, authed).
- TopologyGraphView: lightweight SVG renderer (no new deps), laid out per kind
  (row for pipeline/ring, star for delegation kinds, circle otherwise).
- TopologyExplorer (client): catalog list + roles input → POST
  /api/topologies/build via the /api proxy → render the graph.
- ShellNav: add the Topologies nav item.
- e2e (p8): sign in → nav → browse catalog → build a pipeline → graph renders.

Full suite green (38); npm build + TS clean. Goes live with the next server+
frontend deploy (compare/Pareto UI to follow).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-16 03:41:30 -07:00
co-authored by Claude Opus 4.8
parent a35c82d860
commit 7648487a59
6 changed files with 265 additions and 1 deletions
+33
View File
@@ -0,0 +1,33 @@
import { expect, test, type Page } from "@playwright/test";
// The Topologies page: browse the catalog and build a topology graph against
// the real /api/topologies endpoints (frontend → proxy → backend).
const OWNER_EMAIL = "[email protected]";
const OWNER_PASSWORD = "e2e-password";
async function signIn(page: Page) {
await page.goto("/login");
await page.getByLabel("Email").fill(OWNER_EMAIL);
await page.getByRole("button", { name: /Continue with work email/ }).click();
await page.getByLabel("Password").fill(OWNER_PASSWORD);
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByRole("heading", { name: "Clawmates" })).toBeVisible();
}
test("browse the topology catalog and build a graph", async ({ page }) => {
await signIn(page);
await page.getByRole("link", { name: "Topologies" }).click();
await expect(page).toHaveURL(/\/topologies$/);
await expect(page.getByRole("heading", { name: "Topologies" })).toBeVisible();
// The catalog lists the kinds.
await expect(page.getByRole("button", { name: /hierarchical/i })).toBeVisible();
// Build a pipeline topology and confirm the graph renders.
await page.getByRole("button", { name: /^pipeline$/i }).click();
await page.getByLabel(/Roles/).fill("alpha, beta, gamma");
await page.getByRole("button", { name: "Build" }).click();
await expect(page.locator("svg[aria-label*='topology']")).toBeVisible();
});