diff --git a/packages/client/ui-user-questions/README.i18n.yaml b/packages/client/ui-user-questions/README.i18n.yaml
index dbf890074c..4177fb0a50 100644
--- a/packages/client/ui-user-questions/README.i18n.yaml
+++ b/packages/client/ui-user-questions/README.i18n.yaml
@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: 09388d293f9be9cb
/deepseek-ai-dsh-client-ui-user-questions/use-this-package:
- en: 56368ea7909468c0
- zh: b212ffa1f0fda6a5
+ en: f63c46f9fc2edb1f
+ zh: 82ad1b2e56a61a52
/deepseek-ai-dsh-client-ui-user-questions/use-this-package/answering:
en: 8900fbf9cbd72083
zh: 67a951d58a029a58
diff --git a/packages/client/ui-user-questions/README.md b/packages/client/ui-user-questions/README.md
index e510b510c0..5effc083a8 100644
--- a/packages/client/ui-user-questions/README.md
+++ b/packages/client/ui-user-questions/README.md
@@ -25,7 +25,7 @@ The Web client shows an agent's question beside the chat input. Users choose opt
## Use this package
-When the agent asks a question, the attached card temporarily occupies the composer seat: answer each question, navigate with the pager, or skip it. A single-select question’s first choice marked “Recommended” starts selected, as does every marked choice of a multi-select question; but remains an unsubmitted draft and does not pause a timed countdown. Clicking a single-select choice advances immediately. Enter on a focused option attempts to submit the batch without selecting that option; missing answers return to the first incomplete question. Enter in a text answer continues the flow, while Shift+Enter breaks a line instead (during IME composition Enter only confirms the input candidate without advancing).
+When the agent asks a question, the attached card temporarily occupies the composer seat: answer each question, navigate with the pager, or skip it. When a single-select question’s first choice is marked “Recommended”, it starts selected; in a multi-select question, every marked choice starts selected. These preselections remain unsubmitted drafts and do not pause a timed countdown. Clicking a single-select choice advances immediately. Enter on a focused option attempts to submit the batch without selecting that option; missing answers return to the first incomplete question. Enter in a text answer continues the flow, while Shift+Enter breaks a line instead (during IME composition Enter only confirms the input candidate without advancing).
### Answering
diff --git a/packages/client/ui-user-questions/README.zh.md b/packages/client/ui-user-questions/README.zh.md
index 40b309155b..ad9149c648 100644
--- a/packages/client/ui-user-questions/README.zh.md
+++ b/packages/client/ui-user-questions/README.zh.md
@@ -25,7 +25,7 @@ Web 客户端在聊天输入框旁显示 agent(智能体)的提问。用户
## 使用本包
-当 agent 提问时,附着式卡片会暂时占用编辑器位置:回答每个问题、用翻页器导航,或跳过它。单选题中首个带推荐标记的选项、多选题中所有带推荐标记的选项会预先选中,但仍只是未提交的草稿,不会暂停计时倒计时。点击单选选项会立即前进。聚焦选项时按 Enter 会尝试提交整组答案,不会选中该选项;若有缺失答案,则回到第一道未完成的问题。文本答案中的 Enter 会继续流程,Shift+Enter 则换行(IME 组合输入期间按 Enter 只会确认输入候选,不会前进)。
+当 agent 提问时,附着式卡片会暂时占用编辑器位置:回答每个问题、用翻页器导航,或跳过它。单选题的第一个选项带推荐标记时会预先选中;多选题中所有带推荐标记的选项都会预先选中。这些预选仍只是未提交的草稿,不会暂停计时倒计时。点击单选选项会立即前进。聚焦选项时按 Enter 会尝试提交整组答案,不会选中该选项;若有缺失答案,则回到第一道未完成的问题。文本答案中的 Enter 会继续流程,Shift+Enter 则换行(IME 组合输入期间按 Enter 只会确认输入候选,不会前进)。
### 作答
diff --git a/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx b/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx
index 90cfaecbea..bdb8ce927a 100644
--- a/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx
+++ b/packages/client/ui-user-questions/tests/user-questions-composer.client.spec.tsx
@@ -246,28 +246,50 @@ describe('QuestionComposer', () => {
options: [{ label: 'Unit (Recommended)' }, { label: 'Lint' }, { label: 'Types(推荐)' }],
}])
render()
- expect(screen.getByRole('checkbox', { name: /Unit/ }).getAttribute('aria-checked')).toBe('true')
- expect(screen.getByRole('checkbox', { name: /Lint/ }).getAttribute('aria-checked')).toBe('false')
- expect(screen.getByRole('checkbox', { name: /Types/ }).getAttribute('aria-checked')).toBe('true')
+ expect(screen.getByRole('checkbox', { name: 'Unit' }).getAttribute('aria-checked')).toBe('true')
+ expect(screen.getByRole('checkbox', { name: 'Lint' }).getAttribute('aria-checked')).toBe('false')
+ expect(screen.getByRole('checkbox', { name: 'Types' }).getAttribute('aria-checked')).toBe('true')
- fireEvent.keyDown(screen.getByRole('checkbox', { name: /Lint/ }), { key: 'Enter' })
+ fireEvent.keyDown(screen.getByRole('checkbox', { name: 'Lint' }), { key: 'Enter' })
expect(answer).toHaveBeenCalledWith(answerBatch([
{ id: 'checks', selected: ['Unit (Recommended)', 'Types(推荐)'] },
]))
})
+ it('keeps several recommended multi-select defaults without pausing a timed wait', () => {
+ const { carrier } = wait([
+ {
+ id: 'checks', question: 'Which checks?', multiSelect: true,
+ options: [{ label: 'Unit (Recommended)' }, { label: 'Lint' }, { label: 'Types (Recommended)' }],
+ },
+ { id: 'note', question: 'Anything else?' },
+ ], Date.now() + 30_000)
+ try {
+ const view = render()
+ fireEvent.click(screen.getByLabelText('下一题'))
+ view.unmount()
+ render()
+ expect(carrier.snapshot().countdown?.running).toBe(true)
+ fireEvent.click(screen.getByLabelText('上一题'))
+ expect(screen.getByRole('checkbox', { name: 'Unit' }).getAttribute('aria-checked')).toBe('true')
+ expect(screen.getByRole('checkbox', { name: 'Types' }).getAttribute('aria-checked')).toBe('true')
+ } finally {
+ carrier.timeout()
+ }
+ })
+
it('starts only a recommended first choice of a single-select question selected', () => {
const { carrier } = wait([
{ id: 'first', question: 'First?', options: [{ label: 'A (Recommended)' }, { label: 'B (Recommended)' }] },
{ id: 'later', question: 'Later?', options: [{ label: 'C' }, { label: 'D (Recommended)' }] },
])
render()
- expect(screen.getByRole('radio', { name: /A/ }).getAttribute('aria-checked')).toBe('true')
- expect(screen.getByRole('radio', { name: /B/ }).getAttribute('aria-checked')).toBe('false')
+ expect(screen.getByRole('radio', { name: 'A' }).getAttribute('aria-checked')).toBe('true')
+ expect(screen.getByRole('radio', { name: 'B' }).getAttribute('aria-checked')).toBe('false')
fireEvent.click(screen.getByLabelText('下一题'))
- expect(screen.getByRole('radio', { name: /C/ }).getAttribute('aria-checked')).toBe('false')
- expect(screen.getByRole('radio', { name: /D/ }).getAttribute('aria-checked')).toBe('false')
+ expect(screen.getByRole('radio', { name: 'C' }).getAttribute('aria-checked')).toBe('false')
+ expect(screen.getByRole('radio', { name: 'D' }).getAttribute('aria-checked')).toBe('false')
})
it('treats Enter on an unselected option as submit, without selecting it', () => {
@@ -1023,6 +1045,28 @@ describe('review card', () => {
expect(screen.queryByPlaceholderText('输入你的答案')).toBeNull()
})
+ it('shows recorded multi-select answers without restoring recommended defaults', () => {
+ const questions: PendingQuestion['questions'] = [
+ {
+ id: 'checks', question: 'Which checks?', multiSelect: true,
+ options: [{ label: 'Unit (Recommended)' }, { label: 'Lint' }, { label: 'Types (Recommended)' }],
+ },
+ {
+ id: 'unrecorded', question: 'Which targets?', multiSelect: true,
+ options: [{ label: 'Web (Recommended)' }, { label: 'Desktop' }],
+ },
+ ]
+ const carrier = new PendingQuestion(SID, questions, ToolCallId(REVIEWED_CALL), undefined, [{ id: 'checks', selected: ['Lint'] }])
+ carrier.attachSeat({ hide: vi.fn() })
+ render()
+
+ expect(screen.getByRole('checkbox', { name: 'Unit' }).getAttribute('aria-checked')).toBe('false')
+ expect(screen.getByRole('checkbox', { name: 'Lint' }).getAttribute('aria-checked')).toBe('true')
+ expect(screen.getByRole('checkbox', { name: 'Types' }).getAttribute('aria-checked')).toBe('false')
+ fireEvent.click(screen.getByLabelText('下一题'))
+ expect(screen.getByRole('checkbox', { name: 'Web' }).getAttribute('aria-checked')).toBe('false')
+ })
+
it('reads the record back instead of a draft the live card left under the same key', () => {
kit.actions.replace(PendingQuestion.keyOf(SID, REVIEWED_CALL), {
index: 2,