Q180: a failure no screen handles is shown instead of lost

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 05:51:33 -04:00
1 parent 7a4d986344
commit fd91832bc4
4 files changed
+54

No files matched your search

+1
View File
@@ -694,6 +694,7 @@ export const zh: Record<string, string> = {
"Resets {date} (UTC)": "{date} 重置(UTC)",
"Unsaved": "未保存",
"Working language": "工作语言",
"That did not work": "操作未成功",
"AgentCompany could not load its data.": "AgentCompany 无法加载数据。",
"Retrying every few seconds (attempt {count}).": "每隔几秒重试一次(第 {count} 次)。",
"Retry now": "立即重试",
+2
View File
@@ -1,6 +1,7 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./app/App";
import { FailureToasts } from "./shared/FailureToasts";
import "./styles/fonts.css";
import "./styles.css";
import "./styles/components.css";
@@ -9,5 +10,6 @@ import "./overlays.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
<FailureToasts />
</React.StrictMode>,
);
+39
View File
@@ -0,0 +1,39 @@
import { useEffect, useState } from "react";
import { IconButton } from "./ui";
import { t } from "../i18n/i18n";
const SHOWN = 3;
/** Any failure no screen handled itself (a rejected action or a thrown handler) is shown here until dismissed, so nothing fails without a trace. */
export function FailureToasts() {
const [failures, setFailures] = useState<Array<{ id: number; message: string }>>([]);
useEffect(() => {
let next = 0;
const show = (reason: unknown) => {
const message = reason instanceof Error ? reason.message : String(reason);
setFailures((current) => [...current, { id: next++, message }].slice(-SHOWN));
};
const onRejection = (event: PromiseRejectionEvent) => show(event.reason);
const onError = (event: ErrorEvent) => show(event.error ?? event.message);
window.addEventListener("unhandledrejection", onRejection);
window.addEventListener("error", onError);
return () => {
window.removeEventListener("unhandledrejection", onRejection);
window.removeEventListener("error", onError);
};
}, []);
if (failures.length === 0) return null;
return (
<div style={{ position: "fixed", right: 16, bottom: 16, zIndex: 1000, display: "grid", gap: 8, maxWidth: 420 }}>
{failures.map((failure) => (
<div key={failure.id} role="alert" className="card" style={{ display: "flex", gap: 8, alignItems: "flex-start", borderColor: "var(--red)", padding: 12 }}>
<div style={{ flex: 1, minWidth: 0, fontSize: 13 }}>
<strong style={{ display: "block", marginBottom: 2 }}>{t("That did not work")}</strong>
<span style={{ color: "var(--text-2)", wordBreak: "break-word" }}>{failure.message}</span>
</div>
<IconButton icon="x" label={t("Dismiss")} onClick={() => setFailures((current) => current.filter((item) => item.id !== failure.id))} />
</div>
))}
</div>
);
}
+12
View File
@@ -126,6 +126,18 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await expect(page.getByText("Alex Turing").first()).toBeVisible();
await expect(page.locator(".table-row", { hasText: "Alex Turing" }).locator(".avatar", { hasText: "🚀" })).toBeVisible();
// A failure the dialog does not handle itself still says what went wrong.
await page.getByRole("button", { name: "Hire" }).click();
await page.locator(".modal").getByLabel("Name", { exact: true }).fill("Alex Turing");
await page.locator(".modal").getByLabel("Role", { exact: true }).fill("Engineer");
await page.locator(".modal").getByLabel("Department").fill("Engineering");
await page.locator(".modal").getByRole("button", { name: "Hire" }).click();
const toast = page.getByRole("alert").filter({ hasText: "That did not work" });
await expect(toast).toContainText('An agent named "Alex Turing" is already on staff');
await toast.getByRole("button", { name: "Dismiss" }).click();
await expect(toast).toHaveCount(0);
await page.locator(".modal").getByRole("button", { name: "Cancel" }).click();
// The operator edits their own name and avatar from the sidebar foot.
await page.getByRole("button", { name: "Edit profile" }).click();
await page.locator(".modal").getByLabel("Name", { exact: true }).fill("Dana Zhang");