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,