{t(STATUS[now.status], { value: acceleratorLabel(now.binding) })}
} + {now?.error &&{t(ERRORS[now.error])}
} + {failed &&{t("无法读取或更改 shell 快捷键状态。请重新打开设置后再试。")}
} +{t(description)}
{t(description)}
{t("仍在等待创建完成。你可以继续等待、查看详情,或取消创建。")}
} {stage.phase === "cleanup_failed" &&{t("请先保存任务与附件,在终端核对并处理保留的目录和任务分支。确认处理完成后,在会话菜单中关闭此会话,再从源目录新建会话重试。")}
} diff --git a/desktop/frontend-next/src/ui/accelerator-label.test.ts b/desktop/frontend-next/src/ui/accelerator-label.test.ts new file mode 100644 index 0000000000..fe5573238f --- /dev/null +++ b/desktop/frontend-next/src/ui/accelerator-label.test.ts @@ -0,0 +1,17 @@ +import { afterEach, expect, it, vi } from "vitest"; + +afterEach(() => { vi.unstubAllGlobals(); vi.resetModules(); }); + +it.each([ + ["Macintosh", "⌘⇧Space", "⌃⌥↑"], + ["Windows", "Ctrl+Shift+Space", "Ctrl+Alt+↑"], + ["Linux", "Ctrl+Shift+Space", "Ctrl+Alt+↑"], +])("presents accelerators in %s notation", async (userAgent, primary, explicit) => { + vi.stubGlobal("navigator", { userAgent }); + vi.resetModules(); + // Platform detection happens at module load, after this test supplies the agent. + const { acceleratorLabel } = await import("./keys"); + expect(acceleratorLabel("CommandOrControl+Shift+Space")).toBe(primary); + expect(acceleratorLabel("Control+Alt+Up")).toBe(explicit); + expect(acceleratorLabel("")).toBe(""); +}); diff --git a/desktop/frontend-next/src/ui/cards/outcome.ts b/desktop/frontend-next/src/ui/cards/outcome.ts index 9610cfd1ba..27e6c55649 100644 --- a/desktop/frontend-next/src/ui/cards/outcome.ts +++ b/desktop/frontend-next/src/ui/cards/outcome.ts @@ -40,6 +40,10 @@ export function toolErrorDetail(tool: Tool): string { case "video.ffmpeg_missing": case "video.unsupported_container": case "video.too_large": + case "shell.mixed_mutation_verification": + case "shell.inline_interpreter_not_last": + case "batch.skipped_after_failed_change": + case "batch.check_after_stopped_call": if (toolRefusalReason(tool)) return ""; } return tool.err ?? ""; diff --git a/desktop/frontend-next/src/ui/feedbackdraft.ts b/desktop/frontend-next/src/ui/feedbackdraft.ts index 739423fea4..4a6686619f 100644 --- a/desktop/frontend-next/src/ui/feedbackdraft.ts +++ b/desktop/frontend-next/src/ui/feedbackdraft.ts @@ -10,10 +10,16 @@ export interface FeedbackDraft { const EMPTY: FeedbackDraft = { category: "bug", body: "", contact: "", shots: [] }; -let held: FeedbackDraft = EMPTY; +const CONTACT_KEY = "rx-feedback-contact"; +let held: FeedbackDraft | null = null; export function heldDraft(): FeedbackDraft { - return held; + if (held) return held; + try { + return { ...EMPTY, contact: localStorage.getItem(CONTACT_KEY) ?? "" }; + } catch { + return EMPTY; + } } export function holdDraft(next: FeedbackDraft): void { @@ -21,5 +27,13 @@ export function holdDraft(next: FeedbackDraft): void { } export function dropDraft(): void { - held = EMPTY; + held = null; +} + +export function rememberContact(contact: string): void { + try { + localStorage.setItem(CONTACT_KEY, contact); + } catch { + // Unavailable storage must not turn an accepted report into a failed send. + } } diff --git a/desktop/frontend-next/src/ui/global-shortcut.interaction.test.tsx b/desktop/frontend-next/src/ui/global-shortcut.interaction.test.tsx new file mode 100644 index 0000000000..2231ad1d18 --- /dev/null +++ b/desktop/frontend-next/src/ui/global-shortcut.interaction.test.tsx @@ -0,0 +1,142 @@ +// @vitest-environment jsdom +import { afterEach, beforeEach, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import "./testkit"; +import { boot, STORAGE, t } from "../i18n"; +import type { GlobalShortcutInfo } from "../port/host"; +import { acceleratorLabel } from "./keys"; + +const fixture = vi.hoisted(() => ({ shell: true, now: { binding: "", status: "disabled" } as GlobalShortcutInfo, apply: vi.fn(), read: vi.fn() })); +vi.mock("../port/host", () => ({ host: () => ({ inShell: () => fixture.shell, globalShortcut: fixture.read, setGlobalShortcut: fixture.apply }) })); +import { GlobalShortcutSection } from "./GlobalShortcutSection"; + +beforeEach(() => { + localStorage.clear(); localStorage.setItem(STORAGE, "zh"); boot(); + fixture.shell = true; fixture.now = { binding: "", status: "disabled" }; + fixture.read.mockImplementation(async () => fixture.now); + fixture.apply.mockReset(); +}); +afterEach(cleanup); + +it("is absent in a browser and cannot configure a remote host", async () => { + fixture.shell = false; fixture.now = { binding: "", status: "unsupported" }; + render(