fix(web): break circular links in DegradedProjectState — Back to project and Open dashboard view both point to current page (#1868)
* 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 <noreply@anthropic.com>
---------
Co-authored-by: Aditi Chauhan <aditi1178@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a66a087ef6
commit
50dc18ffa1
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
|
||||
<div className="mt-6 flex flex-wrap gap-3">
|
||||
<Link
|
||||
href={projectDashboardPath(projectId)}
|
||||
href="/"
|
||||
className="rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-sm font-medium text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-bg-elevated-hover)]"
|
||||
>
|
||||
Back to project
|
||||
</Link>
|
||||
<Link
|
||||
href={projectDashboardPath(projectId)}
|
||||
className="rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-sm font-medium text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-bg-elevated-hover)]"
|
||||
>
|
||||
Open dashboard view
|
||||
Back to dashboard
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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<React.AnchorHTMLAttributes<HTMLAnchorElement>>) => (
|
||||
<a {...props}>{children}</a>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/RepairDegradedProjectButton", () => ({
|
||||
RepairDegradedProjectButton: ({ projectId }: { projectId: string }) => (
|
||||
<button>Repair {projectId}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
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(<DegradedProjectState {...baseProps} />);
|
||||
expect(screen.getByText("This project's config failed to load")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a custom heading when provided", () => {
|
||||
render(<DegradedProjectState {...baseProps} heading="Custom heading" />);
|
||||
expect(screen.getByText("Custom heading")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays the resolve error", () => {
|
||||
render(<DegradedProjectState {...baseProps} />);
|
||||
expect(screen.getByText(baseProps.resolveError)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("extracts and displays the config path from the resolve error", () => {
|
||||
render(<DegradedProjectState {...baseProps} />);
|
||||
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(
|
||||
<DegradedProjectState
|
||||
{...baseProps}
|
||||
resolveError="Something went wrong"
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByText("/tmp/my-project/agent-orchestrator.yaml or .yml"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders 'Back to dashboard' link pointing to /", () => {
|
||||
render(<DegradedProjectState {...baseProps} />);
|
||||
const link = screen.getByRole("link", { name: "Back to dashboard" });
|
||||
expect(link).toHaveAttribute("href", "/");
|
||||
});
|
||||
|
||||
it("does not render an 'Edit settings' link", () => {
|
||||
render(<DegradedProjectState {...baseProps} />);
|
||||
expect(screen.queryByRole("link", { name: "Edit settings" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show the repair button for a generic validation error", () => {
|
||||
render(<DegradedProjectState {...baseProps} />);
|
||||
expect(screen.queryByRole("button", { name: /repair/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the repair button when the error indicates a wrapped projects format", () => {
|
||||
render(
|
||||
<DegradedProjectState
|
||||
{...baseProps}
|
||||
resolveError="Local config at /tmp/x/agent-orchestrator.yaml still uses a wrapped projects: format"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByRole("button", { name: /repair/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue