From 50dc18ffa1fe1e7944e214a5f854fba87b1afe61 Mon Sep 17 00:00:00 2001 From: i-trytoohard Date: Thu, 21 May 2026 19:55:17 +0530 Subject: [PATCH] =?UTF-8?q?fix(web):=20break=20circular=20links=20in=20Deg?= =?UTF-8?q?radedProjectState=20=E2=80=94=20Back=20to=20project=20and=20Ope?= =?UTF-8?q?n=20dashboard=20view=20both=20point=20to=20current=20page=20(#1?= =?UTF-8?q?868)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(web): break circular links in DegradedProjectState Both "Back to project" and "Open dashboard view" linked to projectDashboardPath(projectId) — the same page the component is rendered on. Replace with working navigation: - "Back to project" → "/" (global dashboard) - "Open dashboard view" → "/projects/{id}/settings" (edit config) Fixes #1867 * fix(web): break circular links in DegradedProjectState Both "Back to project" and "Open dashboard view" linked to projectDashboardPath(projectId) — the same page the component is rendered on. Replace with working navigation: - "Back to project" → "/" (global dashboard) - "Open dashboard view" → "/projects/{id}/settings" (edit config) Fixes #1867 * fix(web): remove circular 'Edit settings' link from DegradedProjectState The 'Edit settings' link pointed to /projects/{id}/settings, which also renders DegradedProjectState — making the button a self-link on the settings page. Remove it entirely; 'Back to dashboard' (/) is the only escape hatch needed. Add DegradedProjectState component tests and assert both pages show no 'Edit settings' link and that 'Back to dashboard' points to /. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Aditi Chauhan Co-authored-by: Claude Sonnet 4.6 --- .../app/projects/[projectId]/page.test.tsx | 2 + .../[projectId]/settings/page.test.tsx | 2 + .../src/components/DegradedProjectState.tsx | 12 +-- .../__tests__/DegradedProjectState.test.tsx | 86 +++++++++++++++++++ 4 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 packages/web/src/components/__tests__/DegradedProjectState.test.tsx diff --git a/packages/web/src/app/projects/[projectId]/page.test.tsx b/packages/web/src/app/projects/[projectId]/page.test.tsx index 8ba4b4d21..6cdb15ce8 100644 --- a/packages/web/src/app/projects/[projectId]/page.test.tsx +++ b/packages/web/src/app/projects/[projectId]/page.test.tsx @@ -80,5 +80,7 @@ describe("ProjectPage", () => { expect(screen.getByText("This project's config failed to load")).toBeInTheDocument(); expect(screen.getByText("Local config failed validation")).toBeInTheDocument(); expect(screen.queryByTestId("dashboard")).not.toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Back to dashboard" })).toHaveAttribute("href", "/"); + expect(screen.queryByRole("link", { name: "Edit settings" })).not.toBeInTheDocument(); }); }); diff --git a/packages/web/src/app/projects/[projectId]/settings/page.test.tsx b/packages/web/src/app/projects/[projectId]/settings/page.test.tsx index 23686b24c..ae26393c6 100644 --- a/packages/web/src/app/projects/[projectId]/settings/page.test.tsx +++ b/packages/web/src/app/projects/[projectId]/settings/page.test.tsx @@ -77,5 +77,7 @@ describe("ProjectSettingsPage", () => { ).toBeInTheDocument(); expect(screen.getByText("Local config failed validation")).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Save changes" })).not.toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Back to dashboard" })).toHaveAttribute("href", "/"); + expect(screen.queryByRole("link", { name: "Edit settings" })).not.toBeInTheDocument(); }); }); diff --git a/packages/web/src/components/DegradedProjectState.tsx b/packages/web/src/components/DegradedProjectState.tsx index c1b147f63..c02c734a5 100644 --- a/packages/web/src/components/DegradedProjectState.tsx +++ b/packages/web/src/components/DegradedProjectState.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { projectDashboardPath } from "@/lib/routes"; + import { RepairDegradedProjectButton } from "./RepairDegradedProjectButton"; interface DegradedProjectStateProps { @@ -61,16 +61,10 @@ export function DegradedProjectState({
- Back to project - - - Open dashboard view + Back to dashboard
diff --git a/packages/web/src/components/__tests__/DegradedProjectState.test.tsx b/packages/web/src/components/__tests__/DegradedProjectState.test.tsx new file mode 100644 index 000000000..561af112f --- /dev/null +++ b/packages/web/src/components/__tests__/DegradedProjectState.test.tsx @@ -0,0 +1,86 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { DegradedProjectState } from "@/components/DegradedProjectState"; + +vi.mock("next/link", () => ({ + default: ({ + children, + ...props + }: React.PropsWithChildren>) => ( + {children} + ), +})); + +vi.mock("@/components/RepairDegradedProjectButton", () => ({ + RepairDegradedProjectButton: ({ projectId }: { projectId: string }) => ( + + ), +})); + +const baseProps = { + projectId: "my-project", + resolveError: "Local config at /tmp/my-project/agent-orchestrator.yaml failed validation: bad field", + projectPath: "/tmp/my-project", +}; + +describe("DegradedProjectState", () => { + it("renders the default heading", () => { + render(); + expect(screen.getByText("This project's config failed to load")).toBeInTheDocument(); + }); + + it("renders a custom heading when provided", () => { + render(); + expect(screen.getByText("Custom heading")).toBeInTheDocument(); + }); + + it("displays the resolve error", () => { + render(); + expect(screen.getByText(baseProps.resolveError)).toBeInTheDocument(); + }); + + it("extracts and displays the config path from the resolve error", () => { + render(); + expect( + screen.getByText("/tmp/my-project/agent-orchestrator.yaml"), + ).toBeInTheDocument(); + }); + + it("falls back to the project path when the config path cannot be parsed from the error", () => { + render( + , + ); + expect( + screen.getByText("/tmp/my-project/agent-orchestrator.yaml or .yml"), + ).toBeInTheDocument(); + }); + + it("renders 'Back to dashboard' link pointing to /", () => { + render(); + const link = screen.getByRole("link", { name: "Back to dashboard" }); + expect(link).toHaveAttribute("href", "/"); + }); + + it("does not render an 'Edit settings' link", () => { + render(); + expect(screen.queryByRole("link", { name: "Edit settings" })).not.toBeInTheDocument(); + }); + + it("does not show the repair button for a generic validation error", () => { + render(); + expect(screen.queryByRole("button", { name: /repair/i })).not.toBeInTheDocument(); + }); + + it("shows the repair button when the error indicates a wrapped projects format", () => { + render( + , + ); + expect(screen.getByRole("button", { name: /repair/i })).toBeInTheDocument(); + }); +});