diff --git a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx
index 02ab29137..040b0b0dc 100644
--- a/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx
+++ b/apps/ai-game-creator-shell/src/features/agent-runtime/panels.tsx
@@ -251,12 +251,15 @@ export function AgentRuntimeUserInputCard({
placeholder="填写其他答案"
rows={2}
value={answer}
- onChange={(event) =>
+ onChange={(event) => {
+ // React 会在事件派发结束后把 currentTarget 置空,而 setState 的
+ // updater 要等到渲染阶段才跑,所以必须在这里先取出值。
+ const { value } = event.currentTarget;
setAnswers((current) => ({
...current,
- [question.id]: event.currentTarget.value,
- }))
- }
+ [question.id]: value,
+ }));
+ }}
/>
);
diff --git a/apps/ai-game-creator-shell/tests/agentRuntimeUserInputCard.test.tsx b/apps/ai-game-creator-shell/tests/agentRuntimeUserInputCard.test.tsx
new file mode 100644
index 000000000..cc53b7efb
--- /dev/null
+++ b/apps/ai-game-creator-shell/tests/agentRuntimeUserInputCard.test.tsx
@@ -0,0 +1,74 @@
+// @vitest-environment jsdom
+
+import { fireEvent, render, screen } from '@testing-library/react';
+import { StrictMode } from 'react';
+import { describe, expect, it } from 'vitest';
+
+import type { AgentRuntimeUserInputRequest } from '../src/app/types';
+import { AgentRuntimeUserInputCard } from '../src/features/agent-runtime/panels';
+
+function clarificationRequest(): AgentRuntimeUserInputRequest {
+ return {
+ schemaVersion: 'agc.user_input_request.v1',
+ requestId: 'req-1',
+ agentId: 'project-supervisor',
+ taskId: 'task-1',
+ sessionId: 'session-1',
+ runId: 'run-1',
+ actionId: 'action-1',
+ status: 'pending',
+ questions: [
+ {
+ id: 'q1',
+ header: '第1轮·关键决定',
+ question: '这局游戏的重玩动力是什么?',
+ options: [
+ { label: '分数驱动', description: '刷新纪录后重开' },
+ { label: '成长驱动', description: '解锁新能力后重开' },
+ ],
+ },
+ ],
+ allowFreeform: true,
+ responseId: null,
+ requestedAt: 1,
+ updatedAt: 1,
+ };
+}
+
+describe('AgentRuntimeUserInputCard 澄清输入', () => {
+ it('手动输入自由回答不会让页面崩溃', () => {
+ render(
+
+
+ ,
+ );
+
+ const textarea = screen.getByLabelText('第1轮·关键决定 其他回答');
+ fireEvent.change(textarea, { target: { value: '玩家自己写的答案' } });
+
+ expect((textarea as HTMLTextAreaElement).value).toBe('玩家自己写的答案');
+ });
+
+ it('先选中选项再清空填充文本不会让页面崩溃', () => {
+ render(
+
+
+ ,
+ );
+
+ fireEvent.click(screen.getByText('分数驱动'));
+ const textarea = screen.getByLabelText(
+ '第1轮·关键决定 其他回答',
+ ) as HTMLTextAreaElement;
+ expect(textarea.value).toBe('分数驱动');
+
+ fireEvent.change(textarea, { target: { value: '' } });
+ expect(textarea.value).toBe('');
+ });
+});