diff --git a/packages/webui/src/client/features/browser/contracts/browser-port.ts b/packages/webui/src/client/features/browser/contracts/browser-port.ts
index 7e33f8772..0e2214bd6 100644
--- a/packages/webui/src/client/features/browser/contracts/browser-port.ts
+++ b/packages/webui/src/client/features/browser/contracts/browser-port.ts
@@ -239,6 +239,15 @@ export interface BrowserPort {
readonly viewport?: WebuiBrowserViewport;
},
onFrame: (frame: WebuiBrowserImageFrame) => void,
+ /**
+ * The service's refusal, when the stream gave up.
+ *
+ * The frames themselves say nothing about a stream that stopped, so the
+ * refusal is reported here rather than left to the panel to infer: a
+ * panel that is waiting and a panel whose stream failed look identical
+ * until one of them is told.
+ */
+ onFailure?: (error: Error) => void,
) => () => void;
/**
* The session's web tabs, as a snapshot, on a stream.
diff --git a/packages/webui/src/client/features/browser/ui/BrowserPanel.public.tsx b/packages/webui/src/client/features/browser/ui/BrowserPanel.public.tsx
index e4c7828de..86edc8093 100644
--- a/packages/webui/src/client/features/browser/ui/BrowserPanel.public.tsx
+++ b/packages/webui/src/client/features/browser/ui/BrowserPanel.public.tsx
@@ -76,6 +76,10 @@ const PANEL_COPY = {
absent: "当前运行时没有浏览器能力,浏览器面板不可用。",
notReady: "浏览器能力尚未就绪(例如扩展未连接),面板暂时无法显示页面。",
unknown: "当前运行时没有报告浏览器能力,面板按未授权处理。",
+ // The stream gave up. The reason is said once, beside this notice, as a
+ // toast; this sentence says the half that is about the panel — that the
+ // picture is not coming, and that opening the page again is the way back.
+ failed: "浏览器画面流已失败,画面不会再来。重新打开页面可以重试。",
} as const;
export interface BrowserPanelProps {
@@ -411,6 +415,19 @@ export function BrowserPanel({
) : null}
+ {/*
+ A stream that gave up is not a page that is still loading. The empty
+ placeholder below looks the same in both cases, so the failure says
+ so — and it says it here, in the panel, rather than only in a toast
+ that a person can miss. It renders only when the panel failed, so the
+ default DOM is the one that was there before.
+ */}
+ {state?.status === "error" ? (
+