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:
1 parent
7a4d986344
commit
fd91832bc4
4 files changed
+54
No files matched your search
@@ -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": "立即重试",
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user