Q201: an input method's Enter never picks a mention, and suggestions are announced

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-09 10:30:38 -04:00
1 parent 11369f8fed
commit ca7721358e
3 files changed
+50 -3

No files matched your search

+1
View File
@@ -698,6 +698,7 @@ export const zh: Record<string, string> = {
"Working…": "处理中…",
"Waiting for the {name} sign-in in your browser (check {check} of {checks}).": "正在等待你在浏览器中完成 {name} 登录(第 {check}/{checks} 次检查)。",
"Stop waiting": "停止等待",
"People to mention": "可提及的人",
"Layout": "布局",
"View": "视图",
"Run status": "运行状态",
@@ -1,4 +1,5 @@
import { useRef, useState, type MutableRefObject } from "react";
import { t } from "../../i18n/i18n";
import { useId, useRef, useState, type MutableRefObject } from "react";
import { Avatar } from "../../shared/ui";
import type { AgentRecord, EmployeeRecord } from "../../../../shared/domain";
import { canonicalMentionHandle } from "../../../../shared/mention-handles";
@@ -40,6 +41,7 @@ export function MentionTextarea({
}) {
const [query, setQuery] = useState<string | null>(null);
const [index, setIndex] = useState(0);
const listId = useId();
const anchor = useRef(0);
const innerRef = useRef<HTMLTextAreaElement | null>(null);
const ref = textareaRef ?? innerRef;
@@ -52,6 +54,7 @@ export function MentionTextarea({
: [];
const close = () => setQuery(null);
const open = !disabled && query !== null && filtered.length > 0;
const insertMention = (handle: string) => {
const node = ref.current;
@@ -81,6 +84,8 @@ export function MentionTextarea({
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
// While an input method is composing, Enter and Tab confirm its text; they never pick a person or send.
if (event.nativeEvent.isComposing || event.keyCode === 229) return;
if (query !== null && filtered.length > 0) {
if (event.key === "ArrowDown") {
event.preventDefault();
@@ -111,8 +116,8 @@ export function MentionTextarea({
return (
<div style={{ position: "relative" }}>
{!disabled && query !== null && filtered.length > 0 && (
<div style={{
{open && (
<div id={listId} role="listbox" aria-label={t("People to mention")} style={{
position: "absolute",
bottom: "100%",
left: 0,
@@ -129,7 +134,11 @@ export function MentionTextarea({
{filtered.map((c, i) => (
<button
key={c.handle}
id={`${listId}-${i}`}
type="button"
role="option"
aria-selected={i === index}
tabIndex={-1}
onMouseDown={(e) => { e.preventDefault(); insertMention(c.handle); }}
style={{
display: "flex",
@@ -158,6 +167,9 @@ export function MentionTextarea({
rows={rows}
disabled={disabled}
aria-label={ariaLabel}
aria-autocomplete="list"
aria-controls={open ? listId : undefined}
aria-activedescendant={open ? `${listId}-${index}` : undefined}
style={{
width: "100%",
border: 0,
+34
View File
@@ -70,3 +70,37 @@ test("a dialog keeps the keyboard inside it, closes on Escape, and gives focus b
await closeApp(app);
}
});
test("mention suggestions are announced, and an input method's Enter never picks a person", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-e2e-mention-${randomUUID()}`);
mkdirSync(profileDir, { recursive: true });
const builtinDir = seedBuiltinSkillsFixture(join(tmpdir(), `agentcompany-new-e2e-mention-builtin-${randomUUID()}`));
const app = await electron.launch({
args: [resolve("out/main/index.js")],
env: { ...process.env, AGENTCOMPANY_NEW_PROFILE_DIR: profileDir, AGENTCOMPANY_NEW_AUTORUN: "0", AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: builtinDir },
});
try {
const page = await app.firstWindow();
await onboard(page, "Mention Co");
await page.getByRole("button", { name: "Messages" }).click();
const composer = page.getByRole("textbox", { name: "Message" });
await composer.fill("@");
await composer.press("End");
await composer.type("c");
const list = page.getByRole("listbox", { name: "People to mention" });
await expect(list).toBeVisible();
const active = await composer.getAttribute("aria-activedescendant");
expect(active).toBeTruthy();
await expect(list.locator(`[id="${active}"]`)).toHaveAttribute("aria-selected", "true");
// An Enter that confirms composed text (isComposing) leaves the words as typed.
await composer.evaluate((node) => node.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", isComposing: true, bubbles: true, cancelable: true })));
await expect(composer).toHaveValue("@c");
await expect(list).toBeVisible();
// A plain Enter picks the highlighted person.
await composer.press("Enter");
await expect(composer).toHaveValue(/^@\S+ $/);
await expect(list).toHaveCount(0);
} finally {
await closeApp(app);
}
});