Skip to content

Commit 03ec00d

Browse files
committed
fix(web): contain failed right-panel downloads
1 parent ad71d67 commit 03ec00d

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { lazy, Suspense } from "react";
2+
import { expect, it, vi } from "vite-plus/test";
3+
4+
import { render } from "../../test/render";
5+
import { RightPanelContentBoundary } from "./RightPanelContentBoundary";
6+
7+
it("contains a failed panel download and recovers when another surface opens", async () => {
8+
using _error = vi.spyOn(console, "error").mockImplementation(() => {});
9+
const FailedPanel = lazy(async () => {
10+
throw new Error("Panel download failed");
11+
});
12+
await using screen = await render(
13+
<>
14+
<main>Running chat</main>
15+
<RightPanelContentBoundary surfaceId="failed-pr">
16+
<Suspense fallback={<span>Loading panel</span>}>
17+
<FailedPanel />
18+
</Suspense>
19+
</RightPanelContentBoundary>
20+
</>,
21+
);
22+
await expect.element(screen.getByRole("button", { name: "Reload app" })).toBeVisible();
23+
await expect.element(screen.getByRole("main")).toHaveTextContent("Running chat");
24+
25+
await screen.rerender(
26+
<>
27+
<main>Running chat</main>
28+
<RightPanelContentBoundary surfaceId="other-panel">
29+
<p>Other panel</p>
30+
</RightPanelContentBoundary>
31+
</>,
32+
);
33+
await expect.element(screen.getByText("Other panel", { exact: true })).toBeVisible();
34+
await expect.element(screen.getByRole("main")).toHaveTextContent("Running chat");
35+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ReactNode } from "react";
2+
3+
import { RenderErrorBoundary } from "./RenderErrorBoundary";
4+
import { Button } from "./ui/button";
5+
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "./ui/empty";
6+
7+
export function RightPanelContentBoundary(props: {
8+
readonly surfaceId: string;
9+
readonly children: ReactNode;
10+
}) {
11+
return (
12+
<RenderErrorBoundary
13+
resetKeys={[props.surfaceId]}
14+
fallback={
15+
<Empty>
16+
<EmptyHeader>
17+
<EmptyTitle>Could not load this panel</EmptyTitle>
18+
<EmptyDescription>Reload the app to try again.</EmptyDescription>
19+
</EmptyHeader>
20+
<Button variant="outline" size="sm" onClick={() => window.location.reload()}>
21+
Reload app
22+
</Button>
23+
</Empty>
24+
}
25+
>
26+
{props.children}
27+
</RenderErrorBoundary>
28+
);
29+
}

‎apps/web/src/components/RightPanelTabs.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import { resolvePullRequestState } from "./pullRequest/pullRequestPresentation";
104104
import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons";
105105
import { observeResize } from "~/lib/observeResize";
106106
import { surfaceShortcutKey } from "~/lib/surfaceShortcutKey";
107+
import { RightPanelContentBoundary } from "./RightPanelContentBoundary";
107108

108109
export interface RightPanelTabsProps {
109110
mode: PreviewPanelMode;
@@ -1545,7 +1546,9 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
15451546
sideChatDisabledReason={props.sideChatDisabledReason ?? null}
15461547
/>
15471548
) : (
1548-
props.children
1549+
<RightPanelContentBoundary surfaceId={props.activeSurfaceId}>
1550+
{props.children}
1551+
</RightPanelContentBoundary>
15491552
)}
15501553
</div>
15511554
</PreviewPanelShell>

0 commit comments

Comments
 (0)